home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Freelog 115
/
FreelogNo115-MaiJuin2013.iso
/
Internet
/
AvantBrowser
/
asetup.exe
/
_data
/
webkit
/
resources.pak
/
Unnamed File 000298.txt
< prev
next >
Wrap
Text File
|
2013-04-03
|
513KB
|
15,518 lines
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file exists to aggregate all of the javascript used by the
// settings page into a single file which will be flattened and served
// as a single resource.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/////////////////////////////////////////////////////////////////////////////
// Preferences class:
/**
* Preferences class manages access to Chrome profile preferences.
* @constructor
*/
function Preferences() {
// Map of registered preferences.
this.registeredPreferences_ = {};
}
cr.addSingletonGetter(Preferences);
/**
* Sets a Boolean preference and signals its new value.
* @param {string} name Preference name.
* @param {boolean} value New preference value.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.setBooleanPref = function(name, value, commit, metric) {
if (!commit) {
Preferences.getInstance().setPrefNoCommit_(name, 'bool', Boolean(value));
return;
}
var argumentList = [name, Boolean(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setBooleanPref', argumentList);
};
/**
* Sets an integer preference and signals its new value.
* @param {string} name Preference name.
* @param {number} value New preference value.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.setIntegerPref = function(name, value, commit, metric) {
if (!commit) {
Preferences.getInstance().setPrefNoCommit_(name, 'int', Number(value));
return;
}
var argumentList = [name, Number(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setIntegerPref', argumentList);
};
/**
* Sets a double-valued preference and signals its new value.
* @param {string} name Preference name.
* @param {number} value New preference value.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.setDoublePref = function(name, value, commit, metric) {
if (!commit) {
Preferences.getInstance().setPrefNoCommit_(name, 'double', Number(value));
return;
}
var argumentList = [name, Number(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setDoublePref', argumentList);
};
/**
* Sets a string preference and signals its new value.
* @param {string} name Preference name.
* @param {string} value New preference value.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.setStringPref = function(name, value, commit, metric) {
if (!commit) {
Preferences.getInstance().setPrefNoCommit_(name, 'string', String(value));
return;
}
var argumentList = [name, String(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setStringPref', argumentList);
};
/**
* Sets a string preference that represents a URL and signals its new value.
* The value will be fixed to be a valid URL when it gets committed to Chrome.
* @param {string} name Preference name.
* @param {string} value New preference value.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.setURLPref = function(name, value, commit, metric) {
if (!commit) {
Preferences.getInstance().setPrefNoCommit_(name, 'url', String(value));
return;
}
var argumentList = [name, String(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setURLPref', argumentList);
};
/**
* Sets a JSON list preference and signals its new value.
* @param {string} name Preference name.
* @param {Array} value New preference value.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.setListPref = function(name, value, commit, metric) {
if (!commit) {
Preferences.getInstance().setPrefNoCommit_(name, 'list', value);
return;
}
var argumentList = [name, JSON.stringify(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setListPref', argumentList);
};
/**
* Clears the user setting for a preference and signals its new effective
* value.
* @param {string} name Preference name.
* @param {boolean} commit Whether to commit the change to Chrome.
* @param {string} metric User metrics identifier.
*/
Preferences.clearPref = function(name, commit, metric) {
if (!commit) {
Preferences.getInstance().clearPrefNoCommit_(name);
return;
}
var argumentList = [name];
if (metric != undefined) argumentList.push(metric);
chrome.send('clearPref', argumentList);
};
Preferences.prototype = {
__proto__: cr.EventTarget.prototype,
/**
* Adds an event listener to the target.
* @param {string} type The name of the event.
* @param {!Function|{handleEvent:Function}} handler The handler for the
* event. This is called when the event is dispatched.
*/
addEventListener: function(type, handler) {
cr.EventTarget.prototype.addEventListener.call(this, type, handler);
if (!(type in this.registeredPreferences_))
this.registeredPreferences_[type] = {};
},
/**
* Initializes preference reading and change notifications.
*/
initialize: function() {
var params1 = ['Preferences.prefsFetchedCallback'];
var params2 = ['Preferences.prefsChangedCallback'];
for (var prefName in this.registeredPreferences_) {
params1.push(prefName);
params2.push(prefName);
}
chrome.send('fetchPrefs', params1);
chrome.send('observePrefs', params2);
},
/**
* Helper function for flattening of dictionary passed via fetchPrefs
* callback.
* @param {string} prefix Preference name prefix.
* @param {object} dict Map with preference values.
* @private
*/
flattenMapAndDispatchEvent_: function(prefix, dict) {
for (var prefName in dict) {
if (typeof dict[prefName] == 'object' &&
!this.registeredPreferences_[prefix + prefName]) {
this.flattenMapAndDispatchEvent_(prefix + prefName + '.',
dict[prefName]);
} else {
var event = new cr.Event(prefix + prefName);
this.registeredPreferences_[prefix + prefName].orig = dict[prefName];
event.value = dict[prefName];
this.dispatchEvent(event);
}
}
},
/**
* Sets a preference and signals its new value. The change is propagated
* throughout the UI code but is not committed to Chrome yet. The new value
* and its data type are stored so that commitPref() can later be used to
* invoke the appropriate set*Pref() method and actually commit the change.
* @param {string} name Preference name.
* @param {string} type Preference data type.
* @param {*} value New preference value.
* @private
*/
setPrefNoCommit_: function(name, type, value) {
var pref = this.registeredPreferences_[name];
pref.action = 'set';
pref.type = type;
pref.value = value;
var event = new cr.Event(name);
// Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
event.value = {
value: value,
recommendedValue: pref.orig.recommendedValue,
disabled: pref.orig.disabled,
uncommitted: true,
};
this.dispatchEvent(event);
},
/**
* Clears a preference and signals its new value. The change is propagated
* throughout the UI code but is not committed to Chrome yet.
* @param {string} name Preference name.
* @private
*/
clearPrefNoCommit_: function(name) {
var pref = this.registeredPreferences_[name];
pref.action = 'clear';
delete pref.type;
delete pref.value;
var event = new cr.Event(name);
// Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
event.value = {
value: pref.orig.recommendedValue,
controlledBy: 'recommended',
recommendedValue: pref.orig.recommendedValue,
disabled: pref.orig.disabled,
uncommitted: true,
};
this.dispatchEvent(event);
},
/**
* Commits a preference change to Chrome and signals the new preference
* value. Does nothing if there is no uncommitted change.
* @param {string} name Preference name.
* @param {string} metric User metrics identifier.
*/
commitPref: function(name, metric) {
var pref = this.registeredPreferences_[name];
switch (pref.action) {
case 'set':
switch (pref.type) {
case 'bool':
Preferences.setBooleanPref(name, pref.value, true, metric);
break;
case 'int':
Preferences.setIntegerPref(name, pref.value, true, metric);
break;
case 'double':
Preferences.setDoublePref(name, pref.value, true, metric);
break;
case 'string':
Preferences.setStringPref(name, pref.value, true, metric);
break;
case 'url':
Preferences.setURLPref(name, pref.value, true, metric);
break;
case 'list':
Preferences.setListPref(name, pref.value, true, metric);
break;
}
break;
case 'clear':
Preferences.clearPref(name, true, metric);
break;
}
delete pref.action;
delete pref.type;
delete pref.value;
},
/**
* Rolls back a preference change and signals the original preference value.
* Does nothing if there is no uncommitted change.
* @param {string} name Preference name.
*/
rollbackPref: function(name) {
var pref = this.registeredPreferences_[name];
if (!pref.action)
return;
delete pref.action;
delete pref.type;
delete pref.value;
var event = new cr.Event(name);
event.value = pref.orig;
event.value.uncommitted = true;
this.dispatchEvent(event);
}
};
/**
* Callback for fetchPrefs method.
* @param {object} dict Map of fetched property values.
*/
Preferences.prefsFetchedCallback = function(dict) {
Preferences.getInstance().flattenMapAndDispatchEvent_('', dict);
};
/**
* Callback for observePrefs method.
* @param {array} notification An array defining changed preference values.
* notification[0] contains name of the change preference while its new value
* is stored in notification[1].
*/
Preferences.prefsChangedCallback = function(notification) {
var event = new cr.Event(notification[0]);
event.value = notification[1];
prefs = Preferences.getInstance();
prefs.registeredPreferences_[notification[0]] = {orig: notification[1]};
prefs.dispatchEvent(event);
};
// Export
return {
Preferences: Preferences
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var BubbleBase = cr.ui.BubbleBase;
var OptionsBubble = cr.ui.define('div');
OptionsBubble.prototype = {
// Set up the prototype chain.
__proto__: BubbleBase.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
BubbleBase.prototype.decorate.call(this);
this.classList.add('options-bubble');
},
/**
* Set the DOM sibling node, i.e. the node as whose sibling the bubble
* should join the DOM to ensure that focusable elements inside the bubble
* follow the target element in the document's tab order. Only available
* when the bubble is not being shown.
* @param {HTMLElement} node The new DOM sibling node.
*/
set domSibling(node) {
if (!this.hidden)
return;
this.domSibling_ = node;
},
/**
* Show the bubble.
*/
show: function() {
if (!this.hidden)
return;
BubbleBase.prototype.show.call(this);
this.domSibling_.showingBubble = true;
var doc = this.ownerDocument;
this.eventTracker_.add(doc, 'mousewheel', this, true);
this.eventTracker_.add(doc, 'scroll', this, true);
this.eventTracker_.add(doc, 'elementFocused', this, true);
this.eventTracker_.add(window, 'resize', this);
},
/**
* Hide the bubble.
*/
hide: function() {
BubbleBase.prototype.hide.call(this);
this.domSibling_.showingBubble = false;
},
/**
* Handle events, closing the bubble when the user clicks or moves the focus
* outside the bubble and its target element, scrolls the underlying
* document or resizes the window.
* @param {Event} event The event.
*/
handleEvent: function(event) {
BubbleBase.prototype.handleEvent.call(this, event);
switch (event.type) {
// Close the bubble when the user clicks outside it, except if it is a
// left-click on the bubble's target element (allowing the target to
// handle the event and close the bubble itself).
case 'mousedown':
if (event.button == 0 && this.anchorNode_.contains(event.target))
break;
// Close the bubble when the underlying document is scrolled.
case 'mousewheel':
case 'scroll':
if (this.contains(event.target))
break;
// Close the bubble when the window is resized.
case 'resize':
this.hide();
break;
// Close the bubble when the focus moves to an element that is not the
// bubble target and is not inside the bubble.
case 'elementFocused':
if (!this.anchorNode_.contains(event.target) &&
!this.contains(event.target)) {
this.hide();
}
break;
}
},
/**
* Attach the bubble to the document's DOM, making it a sibling of the
* |domSibling_| so that focusable elements inside the bubble follow the
* target element in the document's tab order.
* @private
*/
attachToDOM_: function() {
var parent = this.domSibling_.parentNode;
parent.insertBefore(this, this.domSibling_.nextSibling);
},
};
return {
OptionsBubble: OptionsBubble
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var Preferences = options.Preferences;
/**
* A controlled setting indicator that can be placed on a setting as an
* indicator that the value is controlled by some external entity such as
* policy or an extension.
* @constructor
* @extends {HTMLSpanElement}
*/
var ControlledSettingIndicator = cr.ui.define('span');
ControlledSettingIndicator.prototype = {
__proto__: HTMLSpanElement.prototype,
/**
* Decorates the base element to show the proper icon.
*/
decorate: function() {
var self = this;
// If there is a pref, track its controlledBy and recommendedValue
// properties in order to be able to bring up the correct bubble.
if (this.pref) {
Preferences.getInstance().addEventListener(
this.pref, this.handlePrefChange.bind(this));
this.resetHandler = this.clearAssociatedPref_;
}
this.className = 'controlled-setting-indicator';
this.location = cr.ui.ArrowLocation.TOP_END;
this.image = document.createElement('div');
this.image.tabIndex = 0;
this.image.setAttribute('role', 'button');
this.image.addEventListener('click', this);
this.image.addEventListener('keydown', this);
this.image.addEventListener('mousedown', this);
this.appendChild(this.image);
},
/**
* The given handler will be called when the user clicks on the 'reset to
* recommended value' link shown in the indicator bubble. The |this| object
* will be the indicator itself.
* @param {function()} handler The handler to be called.
*/
set resetHandler(handler) {
this.resetHandler_ = handler;
},
/**
* Whether the indicator is currently showing a bubble.
* @type {boolean}
*/
get showingBubble() {
return this.image.classList.contains('showing-bubble');
},
set showingBubble(showing) {
this.image.classList.toggle('showing-bubble', showing);
},
/**
* Clears the preference associated with this indicator.
* @private
*/
clearAssociatedPref_: function() {
Preferences.clearPref(this.pref, !this.dialogPref);
},
/* Handle changes to the associated pref by hiding any currently visible
* bubble and updating the controlledBy property.
* @param {Event} event Pref change event.
*/
handlePrefChange: function(event) {
OptionsPage.hideBubble();
if (event.value.controlledBy) {
this.controlledBy =
!this.value || String(event.value.value) == this.value ?
event.value.controlledBy : null;
} else if (event.value.recommendedValue != undefined) {
this.controlledBy =
!this.value || String(event.value.recommendedValue) == this.value ?
'hasRecommendation' : null;
} else {
this.controlledBy = null;
}
},
/**
* Handle mouse and keyboard events, allowing the user to open and close a
* bubble with further information.
* @param {Event} event Mouse or keyboard event.
*/
handleEvent: function(event) {
switch (event.type) {
// Toggle the bubble on left click. Let any other clicks propagate.
case 'click':
if (event.button != 0)
return;
break;
// Toggle the bubble when <Return> or <Space> is pressed. Let any other
// key presses propagate.
case 'keydown':
switch (event.keyCode) {
case 13: // Return.
case 32: // Space.
break;
default:
return;
}
break;
// Blur focus when a mouse button is pressed, matching the behavior of
// other Web UI elements.
case 'mousedown':
if (document.activeElement)
document.activeElement.blur();
event.preventDefault();
return;
}
this.toggleBubble_();
event.preventDefault();
event.stopPropagation();
},
/**
* Open or close a bubble with further information about the pref.
* @private
*/
toggleBubble_: function() {
if (this.showingBubble) {
OptionsPage.hideBubble();
} else {
var self = this;
// Construct the bubble text.
if (this.hasAttribute('plural')) {
var defaultStrings = {
'policy': loadTimeData.getString('controlledSettingsPolicy'),
'extension': loadTimeData.getString('controlledSettingsExtension'),
};
} else {
var defaultStrings = {
'policy': loadTimeData.getString('controlledSettingPolicy'),
'extension': loadTimeData.getString('controlledSettingExtension'),
'recommended':
loadTimeData.getString('controlledSettingRecommended'),
'hasRecommendation':
loadTimeData.getString('controlledSettingHasRecommendation'),
};
}
// No controller, no bubble.
if (!this.controlledBy || !(this.controlledBy in defaultStrings))
return;
var text = defaultStrings[this.controlledBy];
// Apply text overrides.
if (this.hasAttribute('text' + this.controlledBy))
text = this.getAttribute('text' + this.controlledBy);
// Create the DOM tree.
var content = document.createElement('div');
content.className = 'controlled-setting-bubble-content';
content.setAttribute('controlled-by', this.controlledBy);
content.textContent = text;
if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ &&
!this.readOnly) {
var container = document.createElement('div');
var action = document.createElement('button');
action.classList.add('link-button');
action.classList.add('controlled-setting-bubble-action');
action.textContent =
loadTimeData.getString('controlledSettingFollowRecommendation');
action.addEventListener('click', function(event) {
self.resetHandler_();
});
container.appendChild(action);
content.appendChild(container);
}
OptionsPage.showBubble(content, this.image, this, this.location);
}
},
};
/**
* The name of the associated preference.
* @type {string}
*/
cr.defineProperty(ControlledSettingIndicator, 'pref', cr.PropertyKind.ATTR);
/**
* Whether this indicator is part of a dialog. If so, changes made to the
* associated preference take effect in the settings UI immediately but are
* only actually committed when the user confirms the dialog. If the user
* cancels the dialog instead, the changes are rolled back in the settings UI
* and never committed.
* @type {boolean}
*/
cr.defineProperty(ControlledSettingIndicator, 'dialogPref',
cr.PropertyKind.BOOL_ATTR);
/**
* The value of the associated preference that the indicator represents. If
* this is not set, the indicator will be visible whenever any value is
* enforced or recommended. If it is set, the indicator will be visible only
* when the enforced or recommended value matches the value it represents.
* This allows multiple indicators to be created for a set of radio buttons,
* ensuring that only one of them is visible at a time.
*/
cr.defineProperty(ControlledSettingIndicator, 'value',
cr.PropertyKind.ATTR);
/**
* The status of the associated preference:
* - 'policy': A specific value is enfoced by policy.
* - 'extension': A specific value is enforced by an extension.
* - 'recommended': A value is recommended by policy. The user could
* override this recommendation but has not done so.
* - 'hasRecommendation': A value is recommended by policy. The user has
* overridden this recommendation.
* - unset: The value is controlled by the user alone.
* @type {string}
*/
cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
cr.PropertyKind.ATTR);
// Export.
return {
ControlledSettingIndicator: ControlledSettingIndicator
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var List = cr.ui.List;
/** @const */ var ListItem = cr.ui.ListItem;
/**
* Creates a deletable list item, which has a button that will trigger a call
* to deleteItemAtIndex(index) in the list.
*/
var DeletableItem = cr.ui.define('li');
DeletableItem.prototype = {
__proto__: ListItem.prototype,
/**
* The element subclasses should populate with content.
* @type {HTMLElement}
* @private
*/
contentElement_: null,
/**
* The close button element.
* @type {HTMLElement}
* @private
*/
closeButtonElement_: null,
/**
* Whether or not this item can be deleted.
* @type {boolean}
* @private
*/
deletable_: true,
/** @override */
decorate: function() {
ListItem.prototype.decorate.call(this);
this.classList.add('deletable-item');
this.contentElement_ = this.ownerDocument.createElement('div');
this.appendChild(this.contentElement_);
this.closeButtonElement_ = this.ownerDocument.createElement('button');
this.closeButtonElement_.className =
'raw-button row-delete-button custom-appearance';
this.closeButtonElement_.addEventListener('mousedown',
this.handleMouseDownUpOnClose_);
this.closeButtonElement_.addEventListener('mouseup',
this.handleMouseDownUpOnClose_);
this.closeButtonElement_.addEventListener('focus',
this.handleFocus_.bind(this));
this.appendChild(this.closeButtonElement_);
},
/**
* Returns the element subclasses should add content to.
* @return {HTMLElement} The element subclasses should popuplate.
*/
get contentElement() {
return this.contentElement_;
},
/**
* Returns the close button element.
* @return {HTMLElement} The close |<button>| element.
*/
get closeButtonElement() {
return this.closeButtonElement_;
},
/* Gets/sets the deletable property. An item that is not deletable doesn't
* show the delete button (although space is still reserved for it).
*/
get deletable() {
return this.deletable_;
},
set deletable(value) {
this.deletable_ = value;
this.closeButtonElement_.disabled = !value;
},
/**
* Called when a focusable child element receives focus. Selects this item
* in the list selection model.
* @private
*/
handleFocus_: function() {
var list = this.parentNode;
var index = list.getIndexOfListItem(this);
list.selectionModel.selectedIndex = index;
list.selectionModel.anchorIndex = index;
},
/**
* Don't let the list have a crack at the event. We don't want clicking the
* close button to change the selection of the list.
* @param {Event} e The mouse down/up event object.
* @private
*/
handleMouseDownUpOnClose_: function(e) {
if (!e.target.disabled)
e.stopPropagation();
},
};
var DeletableItemList = cr.ui.define('list');
DeletableItemList.prototype = {
__proto__: List.prototype,
/** @override */
decorate: function() {
List.prototype.decorate.call(this);
this.addEventListener('click', this.handleClick_);
this.addEventListener('keydown', this.handleKeyDown_);
},
/**
* Callback for onclick events.
* @param {Event} e The click event object.
* @private
*/
handleClick_: function(e) {
if (this.disabled)
return;
var target = e.target;
if (target.classList.contains('row-delete-button')) {
var listItem = this.getListItemAncestor(target);
var selected = this.selectionModel.selectedIndexes;
// Check if the list item that contains the close button being clicked
// is not in the list of selected items. Only delete this item in that
// case.
var idx = this.getIndexOfListItem(listItem);
if (selected.indexOf(idx) == -1) {
this.deleteItemAtIndex(idx);
} else {
this.deleteSelectedItems_();
}
}
},
/**
* Callback for keydown events.
* @param {Event} e The keydown event object.
* @private
*/
handleKeyDown_: function(e) {
// Map delete (and backspace on Mac) to item deletion (unless focus is
// in an input field, where it's intended for text editing).
if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
e.target.tagName != 'INPUT') {
this.deleteSelectedItems_();
// Prevent the browser from going back.
e.preventDefault();
}
},
/**
* Deletes all the currently selected items that are deletable.
* @private
*/
deleteSelectedItems_: function() {
var selected = this.selectionModel.selectedIndexes;
// Reverse through the list of selected indexes to maintain the
// correct index values after deletion.
for (var j = selected.length - 1; j >= 0; j--) {
var index = selected[j];
if (this.getListItemByIndex(index).deletable)
this.deleteItemAtIndex(index);
}
},
/**
* Called when an item should be deleted; subclasses are responsible for
* implementing.
* @param {number} index The index of the item that is being deleted.
*/
deleteItemAtIndex: function(index) {
},
};
return {
DeletableItemList: DeletableItemList,
DeletableItem: DeletableItem,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var EditableTextField = cr.ui.define('div');
/**
* Decorates an element as an editable text field.
* @param {!HTMLElement} el The element to decorate.
*/
EditableTextField.decorate = function(el) {
el.__proto__ = EditableTextField.prototype;
el.decorate();
};
EditableTextField.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* The actual input element in this field.
* @type {?HTMLElement}
* @private
*/
editField_: null,
/**
* The static text displayed when this field isn't editable.
* @type {?HTMLElement}
* @private
*/
staticText_: null,
/**
* The data model for this field.
* @type {?Object}
* @private
*/
model_: null,
/**
* Whether or not the current edit should be considered canceled, rather
* than committed, when editing ends.
* @type {boolean}
* @private
*/
editCanceled_: true,
/** @override */
decorate: function() {
this.classList.add('editable-text-field');
this.createEditableTextCell();
if (this.hasAttribute('i18n-placeholder-text')) {
var identifier = this.getAttribute('i18n-placeholder-text');
var localizedText = loadTimeData.getString(identifier);
if (localizedText)
this.setAttribute('placeholder-text', localizedText);
}
this.addEventListener('keydown', this.handleKeyDown_);
this.editField_.addEventListener('focus', this.handleFocus_.bind(this));
this.editField_.addEventListener('blur', this.handleBlur_.bind(this));
this.checkForEmpty_();
},
/**
* Indicates that this field has no value in the model, and the placeholder
* text (if any) should be shown.
* @type {boolean}
*/
get empty() {
return this.hasAttribute('empty');
},
/**
* The placeholder text to be used when the model or its value is empty.
* @type {string}
*/
get placeholderText() {
return this.getAttribute('placeholder-text');
},
set placeholderText(text) {
if (text)
this.setAttribute('placeholder-text', text);
else
this.removeAttribute('placeholder-text');
this.checkForEmpty_();
},
/**
* Returns the input element in this text field.
* @type {HTMLElement} The element that is the actual input field.
*/
get editField() {
return this.editField_;
},
/**
* Whether the user is currently editing the list item.
* @type {boolean}
*/
get editing() {
return this.hasAttribute('editing');
},
set editing(editing) {
if (this.editing == editing)
return;
if (editing)
this.setAttribute('editing', '');
else
this.removeAttribute('editing');
if (editing) {
this.editCanceled_ = false;
if (this.empty) {
this.removeAttribute('empty');
if (this.editField)
this.editField.value = '';
}
if (this.editField) {
this.editField.focus();
this.editField.select();
}
} else {
if (!this.editCanceled_ && this.hasBeenEdited &&
this.currentInputIsValid) {
this.updateStaticValues_();
cr.dispatchSimpleEvent(this, 'commitedit', true);
} else {
this.resetEditableValues_();
cr.dispatchSimpleEvent(this, 'canceledit', true);
}
this.checkForEmpty_();
}
},
/**
* Whether the item is editable.
* @type {boolean}
*/
get editable() {
return this.hasAttribute('editable');
},
set editable(editable) {
if (this.editable == editable)
return;
if (editable)
this.setAttribute('editable', '');
else
this.removeAttribute('editable');
this.editable_ = editable;
},
/**
* The data model for this field.
* @type {Object}
*/
get model() {
return this.model_;
},
set model(model) {
this.model_ = model;
this.checkForEmpty_(); // This also updates the editField value.
this.updateStaticValues_();
},
/**
* The HTML element that should have focus initially when editing starts,
* if a specific element wasn't clicked. Defaults to the first <input>
* element; can be overridden by subclasses if a different element should be
* focused.
* @type {?HTMLElement}
*/
get initialFocusElement() {
return this.querySelector('input');
},
/**
* Whether the input in currently valid to submit. If this returns false
* when editing would be submitted, either editing will not be ended,
* or it will be cancelled, depending on the context. Can be overridden by
* subclasses to perform input validation.
* @type {boolean}
*/
get currentInputIsValid() {
return true;
},
/**
* Returns true if the item has been changed by an edit. Can be overridden
* by subclasses to return false when nothing has changed to avoid
* unnecessary commits.
* @type {boolean}
*/
get hasBeenEdited() {
return true;
},
/**
* Mutates the input during a successful commit. Can be overridden to
* provide a way to "clean up" valid input so that it conforms to a
* desired format. Will only be called when commit succeeds for valid
* input, or when the model is set.
* @param {string} value Input text to be mutated.
* @return {string} mutated text.
*/
mutateInput: function(value) {
return value;
},
/**
* Creates a div containing an <input>, as well as static text, keeping
* references to them so they can be manipulated.
* @param {string} text The text of the cell.
* @private
*/
createEditableTextCell: function(text) {
// This function should only be called once.
if (this.editField_)
return;
var container = this.ownerDocument.createElement('div');
var textEl = this.ownerDocument.createElement('div');
textEl.className = 'static-text';
textEl.textContent = text;
textEl.setAttribute('displaymode', 'static');
this.appendChild(textEl);
this.staticText_ = textEl;
var inputEl = this.ownerDocument.createElement('input');
inputEl.className = 'editable-text';
inputEl.type = 'text';
inputEl.value = text;
inputEl.setAttribute('displaymode', 'edit');
inputEl.staticVersion = textEl;
this.appendChild(inputEl);
this.editField_ = inputEl;
},
/**
* Resets the editable version of any controls created by
* createEditableTextCell to match the static text.
* @private
*/
resetEditableValues_: function() {
var editField = this.editField_;
var staticLabel = editField.staticVersion;
if (!staticLabel)
return;
if (editField instanceof HTMLInputElement)
editField.value = staticLabel.textContent;
editField.setCustomValidity('');
},
/**
* Sets the static version of any controls created by createEditableTextCell
* to match the current value of the editable version. Called on commit so
* that there's no flicker of the old value before the model updates. Also
* updates the model's value with the mutated value of the edit field.
* @private
*/
updateStaticValues_: function() {
var editField = this.editField_;
var staticLabel = editField.staticVersion;
if (!staticLabel)
return;
if (editField instanceof HTMLInputElement) {
staticLabel.textContent = editField.value;
this.model_.value = this.mutateInput(editField.value);
}
},
/**
* Checks to see if the model or its value are empty. If they are, then set
* the edit field to the placeholder text, if any, and if not, set it to the
* model's value.
* @private
*/
checkForEmpty_: function() {
var editField = this.editField_;
if (!editField)
return;
if (!this.model_ || !this.model_.value) {
this.setAttribute('empty', '');
editField.value = this.placeholderText || '';
} else {
this.removeAttribute('empty');
editField.value = this.model_.value;
}
},
/**
* Called when this widget receives focus.
* @param {Event} e the focus event.
* @private
*/
handleFocus_: function(e) {
if (this.editing)
return;
this.editing = true;
if (this.editField_)
this.editField_.focus();
},
/**
* Called when this widget loses focus.
* @param {Event} e the blur event.
* @private
*/
handleBlur_: function(e) {
if (!this.editing)
return;
this.editing = false;
},
/**
* Called when a key is pressed. Handles committing and canceling edits.
* @param {Event} e The key down event.
* @private
*/
handleKeyDown_: function(e) {
if (!this.editing)
return;
var endEdit;
switch (e.keyIdentifier) {
case 'U+001B': // Esc
this.editCanceled_ = true;
endEdit = true;
break;
case 'Enter':
if (this.currentInputIsValid)
endEdit = true;
break;
}
if (endEdit) {
// Blurring will trigger the edit to end.
this.ownerDocument.activeElement.blur();
// Make sure that handled keys aren't passed on and double-handled.
// (e.g., esc shouldn't both cancel an edit and close a subpage)
e.stopPropagation();
}
},
};
/**
* Takes care of committing changes to EditableTextField items when the
* window loses focus.
*/
window.addEventListener('blur', function(e) {
var itemAncestor = findAncestor(document.activeElement, function(node) {
return node instanceof EditableTextField;
});
if (itemAncestor)
document.activeElement.blur();
});
return {
EditableTextField: EditableTextField,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/**
* Creates a new list item with support for inline editing.
* @constructor
* @extends {options.DeletableListItem}
*/
function InlineEditableItem() {
var el = cr.doc.createElement('div');
InlineEditableItem.decorate(el);
return el;
}
/**
* Decorates an element as a inline-editable list item. Note that this is
* a subclass of DeletableItem.
* @param {!HTMLElement} el The element to decorate.
*/
InlineEditableItem.decorate = function(el) {
el.__proto__ = InlineEditableItem.prototype;
el.decorate();
};
InlineEditableItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* Whether or not this item can be edited.
* @type {boolean}
* @private
*/
editable_: true,
/**
* Whether or not this is a placeholder for adding a new item.
* @type {boolean}
* @private
*/
isPlaceholder_: false,
/**
* Fields associated with edit mode.
* @type {array}
* @private
*/
editFields_: null,
/**
* Whether or not the current edit should be considered cancelled, rather
* than committed, when editing ends.
* @type {boolean}
* @private
*/
editCancelled_: true,
/**
* The editable item corresponding to the last click, if any. Used to decide
* initial focus when entering edit mode.
* @type {HTMLElement}
* @private
*/
editClickTarget_: null,
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
this.editFields_ = [];
this.addEventListener('mousedown', this.handleMouseDown_);
this.addEventListener('keydown', this.handleKeyDown_);
this.addEventListener('leadChange', this.handleLeadChange_);
},
/** @override */
selectionChanged: function() {
this.updateEditState();
},
/**
* Called when this element gains or loses 'lead' status. Updates editing
* mode accordingly.
* @private
*/
handleLeadChange_: function() {
this.updateEditState();
},
/**
* Updates the edit state based on the current selected and lead states.
*/
updateEditState: function() {
if (this.editable)
this.editing = this.selected && this.lead;
},
/**
* Whether the user is currently editing the list item.
* @type {boolean}
*/
get editing() {
return this.hasAttribute('editing');
},
set editing(editing) {
if (this.editing == editing)
return;
if (editing)
this.setAttribute('editing', '');
else
this.removeAttribute('editing');
if (editing) {
this.editCancelled_ = false;
cr.dispatchSimpleEvent(this, 'edit', true);
var focusElement = this.editClickTarget_ || this.initialFocusElement;
this.editClickTarget_ = null;
// When this is called in response to the selectedChange event,
// the list grabs focus immediately afterwards. Thus we must delay
// our focus grab.
var self = this;
if (focusElement) {
window.setTimeout(function() {
// Make sure we are still in edit mode by the time we execute.
if (self.editing) {
focusElement.focus();
focusElement.select();
}
}, 50);
}
} else {
if (!this.editCancelled_ && this.hasBeenEdited &&
this.currentInputIsValid) {
if (this.isPlaceholder)
this.parentNode.focusPlaceholder = true;
this.updateStaticValues_();
cr.dispatchSimpleEvent(this, 'commitedit', true);
} else {
this.resetEditableValues_();
cr.dispatchSimpleEvent(this, 'canceledit', true);
}
}
},
/**
* Whether the item is editable.
* @type {boolean}
*/
get editable() {
return this.editable_;
},
set editable(editable) {
this.editable_ = editable;
if (!editable)
this.editing = false;
},
/**
* Whether the item is a new item placeholder.
* @type {boolean}
*/
get isPlaceholder() {
return this.isPlaceholder_;
},
set isPlaceholder(isPlaceholder) {
this.isPlaceholder_ = isPlaceholder;
if (isPlaceholder)
this.deletable = false;
},
/**
* The HTML element that should have focus initially when editing starts,
* if a specific element wasn't clicked.
* Defaults to the first <input> element; can be overridden by subclasses if
* a different element should be focused.
* @type {HTMLElement}
*/
get initialFocusElement() {
return this.contentElement.querySelector('input');
},
/**
* Whether the input in currently valid to submit. If this returns false
* when editing would be submitted, either editing will not be ended,
* or it will be cancelled, depending on the context.
* Can be overridden by subclasses to perform input validation.
* @type {boolean}
*/
get currentInputIsValid() {
return true;
},
/**
* Returns true if the item has been changed by an edit.
* Can be overridden by subclasses to return false when nothing has changed
* to avoid unnecessary commits.
* @type {boolean}
*/
get hasBeenEdited() {
return true;
},
/**
* Returns a div containing an <input>, as well as static text if
* isPlaceholder is not true.
* @param {string} text The text of the cell.
* @return {HTMLElement} The HTML element for the cell.
* @private
*/
createEditableTextCell: function(text) {
var container = this.ownerDocument.createElement('div');
if (!this.isPlaceholder) {
var textEl = this.ownerDocument.createElement('div');
textEl.className = 'static-text';
textEl.textContent = text;
textEl.setAttribute('displaymode', 'static');
container.appendChild(textEl);
}
var inputEl = this.ownerDocument.createElement('input');
inputEl.type = 'text';
inputEl.value = text;
if (!this.isPlaceholder) {
inputEl.setAttribute('displaymode', 'edit');
inputEl.staticVersion = textEl;
} else {
// At this point |this| is not attached to the parent list yet, so give
// a short timeout in order for the attachment to occur.
var self = this;
window.setTimeout(function() {
var list = self.parentNode;
if (list && list.focusPlaceholder) {
list.focusPlaceholder = false;
if (list.shouldFocusPlaceholder())
inputEl.focus();
}
}, 50);
}
inputEl.addEventListener('focus', this.handleFocus_.bind(this));
container.appendChild(inputEl);
this.editFields_.push(inputEl);
return container;
},
/**
* Resets the editable version of any controls created by createEditable*
* to match the static text.
* @private
*/
resetEditableValues_: function() {
var editFields = this.editFields_;
for (var i = 0; i < editFields.length; i++) {
var staticLabel = editFields[i].staticVersion;
if (!staticLabel && !this.isPlaceholder)
continue;
if (editFields[i].tagName == 'INPUT') {
editFields[i].value =
this.isPlaceholder ? '' : staticLabel.textContent;
}
// Add more tag types here as new createEditable* methods are added.
editFields[i].setCustomValidity('');
}
},
/**
* Sets the static version of any controls created by createEditable*
* to match the current value of the editable version. Called on commit so
* that there's no flicker of the old value before the model updates.
* @private
*/
updateStaticValues_: function() {
var editFields = this.editFields_;
for (var i = 0; i < editFields.length; i++) {
var staticLabel = editFields[i].staticVersion;
if (!staticLabel)
continue;
if (editFields[i].tagName == 'INPUT')
staticLabel.textContent = editFields[i].value;
// Add more tag types here as new createEditable* methods are added.
}
},
/**
* Called when a key is pressed. Handles committing and canceling edits.
* @param {Event} e The key down event.
* @private
*/
handleKeyDown_: function(e) {
if (!this.editing)
return;
var endEdit = false;
switch (e.keyIdentifier) {
case 'U+001B': // Esc
this.editCancelled_ = true;
endEdit = true;
break;
case 'Enter':
if (this.currentInputIsValid)
endEdit = true;
break;
}
if (endEdit) {
// Blurring will trigger the edit to end; see InlineEditableItemList.
this.ownerDocument.activeElement.blur();
// Make sure that handled keys aren't passed on and double-handled.
// (e.g., esc shouldn't both cancel an edit and close a subpage)
e.stopPropagation();
}
},
/**
* Called when the list item is clicked. If the click target corresponds to
* an editable item, stores that item to focus when edit mode is started.
* @param {Event} e The mouse down event.
* @private
*/
handleMouseDown_: function(e) {
if (!this.editable || this.editing)
return;
var clickTarget = e.target;
var editFields = this.editFields_;
for (var i = 0; i < editFields.length; i++) {
if (editFields[i] == clickTarget ||
editFields[i].staticVersion == clickTarget) {
this.editClickTarget_ = editFields[i];
return;
}
}
},
};
/**
* Takes care of committing changes to inline editable list items when the
* window loses focus.
*/
function handleWindowBlurs() {
window.addEventListener('blur', function(e) {
var itemAncestor = findAncestor(document.activeElement, function(node) {
return node instanceof InlineEditableItem;
});
if (itemAncestor)
document.activeElement.blur();
});
}
handleWindowBlurs();
var InlineEditableItemList = cr.ui.define('list');
InlineEditableItemList.prototype = {
__proto__: DeletableItemList.prototype,
/**
* Focuses the input element of the placeholder if true.
* @type {boolean}
*/
focusPlaceholder: false,
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.setAttribute('inlineeditable', '');
this.addEventListener('hasElementFocusChange',
this.handleListFocusChange_);
},
/**
* Called when the list hierarchy as a whole loses or gains focus; starts
* or ends editing for the lead item if necessary.
* @param {Event} e The change event.
* @private
*/
handleListFocusChange_: function(e) {
var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
if (leadItem) {
if (e.newValue)
leadItem.updateEditState();
else
leadItem.editing = false;
}
},
/**
* May be overridden by subclasses to disable focusing the placeholder.
* @return {boolean} True if the placeholder element should be focused on
* edit commit.
*/
shouldFocusPlaceholder: function() {
return true;
},
};
// Export
return {
InlineEditableItem: InlineEditableItem,
InlineEditableItemList: InlineEditableItemList,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/////////////////////////////////////////////////////////////////////////////
// OptionsPage class:
/**
* Base class for options page.
* @constructor
* @param {string} name Options page name.
* @param {string} title Options page title, used for history.
* @extends {EventTarget}
*/
function OptionsPage(name, title, pageDivName) {
this.name = name;
this.title = title;
this.pageDivName = pageDivName;
this.pageDiv = $(this.pageDivName);
this.tab = null;
this.lastFocusedElement = null;
}
/**
* This is the absolute difference maintained between standard and
* fixed-width font sizes. Refer http://crbug.com/91922.
* @const
*/
OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3;
/**
* Offset of page container in pixels, to allow room for side menu.
* Simplified settings pages can override this if they don't use the menu.
* The default (155) comes from -webkit-margin-start in uber_shared.css
* @private
*/
OptionsPage.horizontalOffset = 155;
/**
* Main level option pages. Maps lower-case page names to the respective page
* object.
* @protected
*/
OptionsPage.registeredPages = {};
/**
* Pages which are meant to behave like modal dialogs. Maps lower-case overlay
* names to the respective overlay object.
* @protected
*/
OptionsPage.registeredOverlayPages = {};
/**
* Gets the default page (to be shown on initial load).
*/
OptionsPage.getDefaultPage = function() {
return BrowserOptions.getInstance();
};
/**
* Shows the default page.
*/
OptionsPage.showDefaultPage = function() {
this.navigateToPage(this.getDefaultPage().name);
};
/**
* "Navigates" to a page, meaning that the page will be shown and the
* appropriate entry is placed in the history.
* @param {string} pageName Page name.
*/
OptionsPage.navigateToPage = function(pageName) {
this.showPageByName(pageName, true);
};
/**
* Shows a registered page. This handles both top-level and overlay pages.
* @param {string} pageName Page name.
* @param {boolean} updateHistory True if we should update the history after
* showing the page.
* @param {Object=} opt_propertyBag An optional bag of properties including
* replaceState (if history state should be replaced instead of pushed).
* @private
*/
OptionsPage.showPageByName = function(pageName,
updateHistory,
opt_propertyBag) {
// If |opt_propertyBag| is non-truthy, homogenize to object.
opt_propertyBag = opt_propertyBag || {};
// If a bubble is currently being shown, hide it.
this.hideBubble();
// Find the currently visible root-level page.
var rootPage = null;
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (page.visible && !page.parentPage) {
rootPage = page;
break;
}
}
// Find the target page.
var targetPage = this.registeredPages[pageName.toLowerCase()];
if (!targetPage || !targetPage.canShowPage()) {
// If it's not a page, try it as an overlay.
if (!targetPage && this.showOverlay_(pageName, rootPage)) {
if (updateHistory)
this.updateHistoryState_(!!opt_propertyBag.replaceState);
return;
} else {
targetPage = this.getDefaultPage();
}
}
pageName = targetPage.name.toLowerCase();
var targetPageWasVisible = targetPage.visible;
// Determine if the root page is 'sticky', meaning that it
// shouldn't change when showing an overlay. This can happen for special
// pages like Search.
var isRootPageLocked =
rootPage && rootPage.sticky && targetPage.parentPage;
var allPageNames = Array.prototype.concat.call(
Object.keys(this.registeredPages),
Object.keys(this.registeredOverlayPages));
// Notify pages if they will be hidden.
for (var i = 0; i < allPageNames.length; ++i) {
var name = allPageNames[i];
var page = this.registeredPages[name] ||
this.registeredOverlayPages[name];
if (!page.parentPage && isRootPageLocked)
continue;
if (page.willHidePage && name != pageName &&
!page.isAncestorOfPage(targetPage)) {
page.willHidePage();
}
}
// Update visibilities to show only the hierarchy of the target page.
for (var i = 0; i < allPageNames.length; ++i) {
var name = allPageNames[i];
var page = this.registeredPages[name] ||
this.registeredOverlayPages[name];
if (!page.parentPage && isRootPageLocked)
continue;
page.visible = name == pageName || page.isAncestorOfPage(targetPage);
}
// Update the history and current location.
if (updateHistory)
this.updateHistoryState_(!!opt_propertyBag.replaceState);
// Update tab title.
this.setTitle_(targetPage.title);
// Update focus if any other control was focused before.
if (document.activeElement != document.body)
targetPage.focus();
// Notify pages if they were shown.
for (var i = 0; i < allPageNames.length; ++i) {
var name = allPageNames[i];
var page = this.registeredPages[name] ||
this.registeredOverlayPages[name];
if (!page.parentPage && isRootPageLocked)
continue;
if (!targetPageWasVisible && page.didShowPage &&
(name == pageName || page.isAncestorOfPage(targetPage))) {
page.didShowPage();
}
}
};
/**
* Sets the title of the page. This is accomplished by calling into the
* parent page API.
* @param {String} title The title string.
* @private
*/
OptionsPage.setTitle_ = function(title) {
uber.invokeMethodOnParent('setTitle', {title: title});
};
/**
* Scrolls the page to the correct position (the top when opening an overlay,
* or the old scroll position a previously hidden overlay becomes visible).
* @private
*/
OptionsPage.updateScrollPosition_ = function() {
var container = $('page-container');
var scrollTop = container.oldScrollTop || 0;
container.oldScrollTop = undefined;
window.scroll(document.body.scrollLeft, scrollTop);
};
/**
* Pushes the current page onto the history stack, overriding the last page
* if it is the generic chrome://settings/.
* @param {boolean} replace If true, allow no history events to be created.
* @param {object=} opt_params A bag of optional params, including:
* {boolean} ignoreHash Whether to include the hash or not.
* @private
*/
OptionsPage.updateHistoryState_ = function(replace, opt_params) {
var page = this.getTopmostVisiblePage();
var path = window.location.pathname + window.location.hash;
if (path)
path = path.slice(1).replace(/\/(?:#|$)/, ''); // Remove trailing slash.
// Update tab title.
this.setTitle_(page.title);
// The page is already in history (the user may have clicked the same link
// twice). Do nothing.
if (path == page.name &&
!document.documentElement.classList.contains('loading')) {
return;
}
var hash = opt_params && opt_params.ignoreHash ? '' : window.location.hash;
// If settings are embedded, tell the outer page to set its "path" to the
// inner frame's path.
var outerPath = (page == this.getDefaultPage() ? '' : page.name) + hash;
uber.invokeMethodOnParent('setPath', {path: outerPath});
// If there is no path, the current location is chrome://settings/.
// Override this with the new page.
var historyFunction = path && !replace ? window.history.pushState :
window.history.replaceState;
historyFunction.call(window.history,
{pageName: page.name},
page.title,
'/' + page.name + hash);
};
/**
* Shows a registered Overlay page. Does not update history.
* @param {string} overlayName Page name.
* @param {OptionPage} rootPage The currently visible root-level page.
* @return {boolean} whether we showed an overlay.
*/
OptionsPage.showOverlay_ = function(overlayName, rootPage) {
var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
if (!overlay || !overlay.canShowPage())
return false;
// Save the currently focused element in the page for restoration later.
var currentPage = this.getTopmostVisiblePage();
if (currentPage)
currentPage.lastFocusedElement = document.activeElement;
if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
this.showPageByName(overlay.parentPage.name, false);
if (!overlay.visible) {
overlay.visible = true;
if (overlay.didShowPage) overlay.didShowPage();
}
// Update tab title.
this.setTitle_(overlay.title);
// Change focus to the overlay if any other control was focused before.
if (document.activeElement != document.body)
overlay.focus();
$('searchBox').setAttribute('aria-hidden', true);
if ($('search-field').value == '') {
var section = overlay.associatedSection;
if (section)
options.BrowserOptions.scrollToSection(section);
}
return true;
};
/**
* Returns whether or not an overlay is visible.
* @return {boolean} True if an overlay is visible.
* @private
*/
OptionsPage.isOverlayVisible_ = function() {
return this.getVisibleOverlay_() != null;
};
/**
* Returns the currently visible overlay, or null if no page is visible.
* @return {OptionPage} The visible overlay.
*/
OptionsPage.getVisibleOverlay_ = function() {
var topmostPage = null;
for (var name in this.registeredOverlayPages) {
var page = this.registeredOverlayPages[name];
if (page.visible &&
(!topmostPage || page.nestingLevel > topmostPage.nestingLevel)) {
topmostPage = page;
}
}
return topmostPage;
};
/**
* Restores the last focused element on a given page.
*/
OptionsPage.restoreLastFocusedElement_ = function() {
var currentPage = this.getTopmostVisiblePage();
if (currentPage.lastFocusedElement)
currentPage.lastFocusedElement.focus();
};
/**
* Closes the visible overlay. Updates the history state after closing the
* overlay.
*/
OptionsPage.closeOverlay = function() {
var overlay = this.getVisibleOverlay_();
if (!overlay)
return;
overlay.visible = false;
if (overlay.didClosePage) overlay.didClosePage();
this.updateHistoryState_(false, {ignoreHash: true});
this.restoreLastFocusedElement_();
if (!this.isOverlayVisible_())
$('searchBox').removeAttribute('aria-hidden');
};
/**
* Cancels (closes) the overlay, due to the user pressing <Esc>.
*/
OptionsPage.cancelOverlay = function() {
// Blur the active element to ensure any changed pref value is saved.
document.activeElement.blur();
var overlay = this.getVisibleOverlay_();
// Let the overlay handle the <Esc> if it wants to.
if (overlay.handleCancel) {
overlay.handleCancel();
this.restoreLastFocusedElement_();
} else {
this.closeOverlay();
}
};
/**
* Hides the visible overlay. Does not affect the history state.
* @private
*/
OptionsPage.hideOverlay_ = function() {
var overlay = this.getVisibleOverlay_();
if (overlay)
overlay.visible = false;
};
/**
* Returns the pages which are currently visible, ordered by nesting level
* (ascending).
* @return {Array.OptionPage} The pages which are currently visible, ordered
* by nesting level (ascending).
*/
OptionsPage.getVisiblePages_ = function() {
var visiblePages = [];
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (page.visible)
visiblePages[page.nestingLevel] = page;
}
return visiblePages;
};
/**
* Returns the topmost visible page (overlays excluded).
* @return {OptionPage} The topmost visible page aside any overlay.
* @private
*/
OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
var topPage = null;
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (page.visible &&
(!topPage || page.nestingLevel > topPage.nestingLevel))
topPage = page;
}
return topPage;
};
/**
* Returns the topmost visible page, or null if no page is visible.
* @return {OptionPage} The topmost visible page.
*/
OptionsPage.getTopmostVisiblePage = function() {
// Check overlays first since they're top-most if visible.
return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
};
/**
* Returns the currently visible bubble, or null if no bubble is visible.
* @return {OptionsBubble} The bubble currently being shown.
*/
OptionsPage.getVisibleBubble = function() {
var bubble = OptionsPage.bubble_;
return bubble && !bubble.hidden ? bubble : null;
};
/**
* Shows an informational bubble displaying |content| and pointing at the
* |target| element. If |content| has focusable elements, they join the
* current page's tab order as siblings of |domSibling|.
* @param {HTMLDivElement} content The content of the bubble.
* @param {HTMLElement} target The element at which the bubble points.
* @param {HTMLElement} domSibling The element after which the bubble is added
* to the DOM.
* @param {cr.ui.ArrowLocation} location The arrow location.
*/
OptionsPage.showBubble = function(content, target, domSibling, location) {
OptionsPage.hideBubble();
var bubble = new options.OptionsBubble;
bubble.anchorNode = target;
bubble.domSibling = domSibling;
bubble.arrowLocation = location;
bubble.content = content;
bubble.show();
OptionsPage.bubble_ = bubble;
};
/**
* Hides the currently visible bubble, if any.
*/
OptionsPage.hideBubble = function() {
if (OptionsPage.bubble_)
OptionsPage.bubble_.hide();
};
/**
* Shows the tab contents for the given navigation tab.
* @param {!Element} tab The tab that the user clicked.
*/
OptionsPage.showTab = function(tab) {
// Search parents until we find a tab, or the nav bar itself. This allows
// tabs to have child nodes, e.g. labels in separately-styled spans.
while (tab && !tab.classList.contains('subpages-nav-tabs') &&
!tab.classList.contains('tab')) {
tab = tab.parentNode;
}
if (!tab || !tab.classList.contains('tab'))
return;
// Find tab bar of the tab.
var tabBar = tab;
while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
tabBar = tabBar.parentNode;
}
if (!tabBar)
return;
if (tabBar.activeNavTab != null) {
tabBar.activeNavTab.classList.remove('active-tab');
$(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
remove('active-tab-contents');
}
tab.classList.add('active-tab');
$(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
tabBar.activeNavTab = tab;
};
/**
* Registers new options page.
* @param {OptionsPage} page Page to register.
*/
OptionsPage.register = function(page) {
this.registeredPages[page.name.toLowerCase()] = page;
page.initializePage();
};
/**
* Find an enclosing section for an element if it exists.
* @param {Element} element Element to search.
* @return {OptionPage} The section element, or null.
* @private
*/
OptionsPage.findSectionForNode_ = function(node) {
while (node = node.parentNode) {
if (node.nodeName == 'SECTION')
return node;
}
return null;
};
/**
* Registers a new Overlay page.
* @param {OptionsPage} overlay Overlay to register.
* @param {OptionsPage} parentPage Associated parent page for this overlay.
* @param {Array} associatedControls Array of control elements associated with
* this page.
*/
OptionsPage.registerOverlay = function(overlay,
parentPage,
associatedControls) {
this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
overlay.parentPage = parentPage;
if (associatedControls) {
overlay.associatedControls = associatedControls;
if (associatedControls.length) {
overlay.associatedSection =
this.findSectionForNode_(associatedControls[0]);
}
// Sanity check.
for (var i = 0; i < associatedControls.length; ++i) {
assert(associatedControls[i], 'Invalid element passed.');
}
}
// Reverse the button strip for views. See the documentation of
// reverseButtonStrip_() for an explanation of why this is necessary.
if (cr.isViews)
this.reverseButtonStrip_(overlay);
overlay.tab = undefined;
overlay.isOverlay = true;
overlay.initializePage();
};
/**
* Reverses the child elements of a button strip. This is necessary because
* WebKit does not alter the tab order for elements that are visually reversed
* using -webkit-box-direction: reverse, and the button order is reversed for
* views. See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
* information.
* @param {Object} overlay The overlay containing the button strip to reverse.
* @private
*/
OptionsPage.reverseButtonStrip_ = function(overlay) {
var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
// Reverse all button-strips in the overlay.
for (var j = 0; j < buttonStrips.length; j++) {
var buttonStrip = buttonStrips[j];
var childNodes = buttonStrip.childNodes;
for (var i = childNodes.length - 1; i >= 0; i--)
buttonStrip.appendChild(childNodes[i]);
}
};
/**
* Callback for window.onpopstate.
* @param {Object} data State data pushed into history.
*/
OptionsPage.setState = function(data) {
if (data && data.pageName) {
this.willClose();
this.showPageByName(data.pageName, false);
}
};
/**
* Callback for window.onbeforeunload. Used to notify overlays that they will
* be closed.
*/
OptionsPage.willClose = function() {
var overlay = this.getVisibleOverlay_();
if (overlay && overlay.didClosePage)
overlay.didClosePage();
};
/**
* Freezes/unfreezes the scroll position of the root page container.
* @param {boolean} freeze Whether the page should be frozen.
* @private
*/
OptionsPage.setRootPageFrozen_ = function(freeze) {
var container = $('page-container');
if (container.classList.contains('frozen') == freeze)
return;
if (freeze) {
// Lock the width, since auto width computation may change.
container.style.width = window.getComputedStyle(container).width;
container.oldScrollTop = document.body.scrollTop;
container.classList.add('frozen');
var verticalPosition =
container.getBoundingClientRect().top - container.oldScrollTop;
container.style.top = verticalPosition + 'px';
this.updateFrozenElementHorizontalPosition_(container);
} else {
container.classList.remove('frozen');
container.style.top = '';
container.style.left = '';
container.style.right = '';
container.style.width = '';
}
};
/**
* Freezes/unfreezes the scroll position of the root page based on the current
* page stack.
*/
OptionsPage.updateRootPageFreezeState = function() {
var topPage = OptionsPage.getTopmostVisiblePage();
if (topPage)
this.setRootPageFrozen_(topPage.isOverlay);
};
/**
* Initializes the complete options page. This will cause all C++ handlers to
* be invoked to do final setup.
*/
OptionsPage.initialize = function() {
chrome.send('coreOptionsInitialize');
uber.onContentFrameLoaded();
document.addEventListener('scroll', this.handleScroll_.bind(this));
// Trigger the scroll handler manually to set the initial state.
this.handleScroll_();
// Shake the dialog if the user clicks outside the dialog bounds.
var containers = [$('overlay-container-1'), $('overlay-container-2')];
for (var i = 0; i < containers.length; i++) {
var overlay = containers[i];
cr.ui.overlay.setupOverlay(overlay);
overlay.addEventListener('cancelOverlay',
OptionsPage.cancelOverlay.bind(OptionsPage));
}
};
/**
* Does a bounds check for the element on the given x, y client coordinates.
* @param {Element} e The DOM element.
* @param {number} x The client X to check.
* @param {number} y The client Y to check.
* @return {boolean} True if the point falls within the element's bounds.
* @private
*/
OptionsPage.elementContainsPoint_ = function(e, x, y) {
var clientRect = e.getBoundingClientRect();
return x >= clientRect.left && x <= clientRect.right &&
y >= clientRect.top && y <= clientRect.bottom;
};
/**
* Called when the page is scrolled; moves elements that are position:fixed
* but should only behave as if they are fixed for vertical scrolling.
* @private
*/
OptionsPage.handleScroll_ = function() {
this.updateAllFrozenElementPositions_();
};
/**
* Updates all frozen pages to match the horizontal scroll position.
* @private
*/
OptionsPage.updateAllFrozenElementPositions_ = function() {
var frozenElements = document.querySelectorAll('.frozen');
for (var i = 0; i < frozenElements.length; i++)
this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
};
/**
* Updates the given frozen element to match the horizontal scroll position.
* @param {HTMLElement} e The frozen element to update.
* @private
*/
OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
if (isRTL()) {
e.style.right = OptionsPage.horizontalOffset + 'px';
} else {
e.style.left = OptionsPage.horizontalOffset -
document.body.scrollLeft + 'px';
}
};
/**
* Change the horizontal offset used to reposition elements while showing an
* overlay from the default.
*/
OptionsPage.setHorizontalOffset = function(value) {
OptionsPage.horizontalOffset = value;
};
OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
if (enabled) {
document.documentElement.setAttribute(
'flashPluginSupportsClearSiteData', '');
} else {
document.documentElement.removeAttribute(
'flashPluginSupportsClearSiteData');
}
};
OptionsPage.setPepperFlashSettingsEnabled = function(enabled) {
if (enabled) {
document.documentElement.setAttribute(
'enablePepperFlashSettings', '');
} else {
document.documentElement.removeAttribute(
'enablePepperFlashSettings');
}
};
OptionsPage.prototype = {
__proto__: cr.EventTarget.prototype,
/**
* The parent page of this option page, or null for top-level pages.
* @type {OptionsPage}
*/
parentPage: null,
/**
* The section on the parent page that is associated with this page.
* Can be null.
* @type {Element}
*/
associatedSection: null,
/**
* An array of controls that are associated with this page. The first
* control should be located on a top-level page.
* @type {OptionsPage}
*/
associatedControls: null,
/**
* Initializes page content.
*/
initializePage: function() {},
/**
* Sets focus on the first focusable element. Override for a custom focus
* strategy.
*/
focus: function() {
var elements = this.pageDiv.querySelectorAll(
'input, list, select, textarea, button');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
// Try to focus. If fails, then continue.
element.focus();
if (document.activeElement == element)
return;
}
},
/**
* Gets the container div for this page if it is an overlay.
* @type {HTMLElement}
*/
get container() {
assert(this.isOverlay);
return this.pageDiv.parentNode;
},
/**
* Gets page visibility state.
* @type {boolean}
*/
get visible() {
// If this is an overlay dialog it is no longer considered visible while
// the overlay is fading out. See http://crbug.com/118629.
if (this.isOverlay &&
this.container.classList.contains('transparent')) {
return false;
}
return !this.pageDiv.hidden;
},
/**
* Sets page visibility.
* @type {boolean}
*/
set visible(visible) {
if ((this.visible && visible) || (!this.visible && !visible))
return;
// If using an overlay, the visibility of the dialog is toggled at the
// same time as the overlay to show the dialog's out transition. This
// is handled in setOverlayVisible.
if (this.isOverlay) {
this.setOverlayVisible_(visible);
} else {
this.pageDiv.hidden = !visible;
this.onVisibilityChanged_();
}
cr.dispatchPropertyChange(this, 'visible', visible, !visible);
},
/**
* Shows or hides an overlay (including any visible dialog).
* @param {boolean} visible Whether the overlay should be visible or not.
* @private
*/
setOverlayVisible_: function(visible) {
assert(this.isOverlay);
var pageDiv = this.pageDiv;
var container = this.container;
if (visible) {
uber.invokeMethodOnParent('beginInterceptingEvents');
this.pageDiv.removeAttribute('aria-hidden');
if (this.parentPage)
this.parentPage.pageDiv.setAttribute('aria-hidden', true);
} else {
if (this.parentPage)
this.parentPage.pageDiv.removeAttribute('aria-hidden');
}
if (container.hidden != visible) {
if (visible) {
// If the container is set hidden and then immediately set visible
// again, the fadeCompleted_ callback would cause it to be erroneously
// hidden again. Removing the transparent tag avoids that.
container.classList.remove('transparent');
// Hide all dialogs in this container since a different one may have
// been previously visible before fading out.
var pages = container.querySelectorAll('.page');
for (var i = 0; i < pages.length; i++)
pages[i].hidden = true;
// Show the new dialog.
pageDiv.hidden = false;
}
return;
}
if (visible) {
container.hidden = false;
pageDiv.hidden = false;
// NOTE: This is a hacky way to force the container to layout which
// will allow us to trigger the webkit transition.
container.scrollTop;
container.classList.remove('transparent');
this.onVisibilityChanged_();
} else {
var self = this;
// TODO: Use an event delegate to avoid having to subscribe and
// unsubscribe for webkitTransitionEnd events.
container.addEventListener('webkitTransitionEnd', function f(e) {
if (e.target != e.currentTarget || e.propertyName != 'opacity')
return;
container.removeEventListener('webkitTransitionEnd', f);
self.fadeCompleted_();
});
container.classList.add('transparent');
}
},
/**
* Called when a container opacity transition finishes.
* @private
*/
fadeCompleted_: function() {
if (this.container.classList.contains('transparent')) {
this.pageDiv.hidden = true;
this.container.hidden = true;
this.onVisibilityChanged_();
if (this.nestingLevel == 1)
uber.invokeMethodOnParent('stopInterceptingEvents');
}
},
/**
* Called when a page is shown or hidden to update the root options page
* based on this page's visibility.
* @private
*/
onVisibilityChanged_: function() {
OptionsPage.updateRootPageFreezeState();
if (this.isOverlay && !this.visible)
OptionsPage.updateScrollPosition_();
},
/**
* The nesting level of this page.
* @type {number} The nesting level of this page (0 for top-level page)
*/
get nestingLevel() {
var level = 0;
var parent = this.parentPage;
while (parent) {
level++;
parent = parent.parentPage;
}
return level;
},
/**
* Whether the page is considered 'sticky', such that it will
* remain a top-level page even if sub-pages change.
* @type {boolean} True if this page is sticky.
*/
get sticky() {
return false;
},
/**
* Checks whether this page is an ancestor of the given page in terms of
* subpage nesting.
* @param {OptionsPage} page The potential descendent of this page.
* @return {boolean} True if |page| is nested under this page.
*/
isAncestorOfPage: function(page) {
var parent = page.parentPage;
while (parent) {
if (parent == this)
return true;
parent = parent.parentPage;
}
return false;
},
/**
* Whether it should be possible to show the page.
* @return {boolean} True if the page should be shown.
*/
canShowPage: function() {
return true;
},
};
// Export
return {
OptionsPage: OptionsPage
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var Preferences = options.Preferences;
/**
* Allows an element to be disabled for several reasons.
* The element is disabled if at least one reason is true, and the reasons
* can be set separately.
* @private
* @param {!HTMLElement} el The element to update.
* @param {string} reason The reason for disabling the element.
* @param {boolean} disabled Whether the element should be disabled or enabled
* for the given |reason|.
*/
function updateDisabledState_(el, reason, disabled) {
if (!el.disabledReasons)
el.disabledReasons = {};
if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) {
// The element has been previously disabled without a reason, so we add
// one to keep it disabled.
el.disabledReasons.other = true;
}
if (!el.disabled) {
// If the element is not disabled, there should be no reason, except for
// 'other'.
delete el.disabledReasons.other;
if (Object.keys(el.disabledReasons).length > 0)
console.error('Element is not disabled but should be');
}
if (disabled) {
el.disabledReasons[reason] = true;
} else {
delete el.disabledReasons[reason];
}
el.disabled = Object.keys(el.disabledReasons).length > 0;
}
/////////////////////////////////////////////////////////////////////////////
// PrefInputElement class:
// Define a constructor that uses an input element as its underlying element.
var PrefInputElement = cr.ui.define('input');
PrefInputElement.prototype = {
// Set up the prototype chain
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
var self = this;
// Listen for user events.
this.addEventListener('change', this.handleChange_.bind(this));
// Listen for pref changes.
Preferences.getInstance().addEventListener(this.pref, function(event) {
if (event.value.uncommitted && !self.dialogPref)
return;
self.updateStateFromPref_(event);
updateDisabledState_(self, 'notUserModifiable', event.value.disabled);
self.controlledBy = event.value.controlledBy;
});
},
/**
* Handle changes to the input element's state made by the user. If a custom
* change handler does not suppress it, a default handler is invoked that
* updates the associated pref.
* @param {Event} event Change event.
* @private
*/
handleChange_: function(event) {
if (!this.customChangeHandler(event))
this.updatePrefFromState_();
},
/**
* Update the input element's state when the associated pref changes.
* @param {Event} event Pref change event.
* @private
*/
updateStateFromPref_: function(event) {
this.value = event.value.value;
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
/**
* Custom change handler that is invoked first when the user makes changes
* to the input element's state. If it returns false, a default handler is
* invoked next that updates the associated pref. If it returns true, the
* default handler is suppressed (i.e., this works like stopPropagation or
* cancelBubble).
* @param {Event} event Input element change event.
*/
customChangeHandler: function(event) {
return false;
},
};
/**
* The name of the associated preference.
* @type {string}
*/
cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
/**
* The data type of the associated preference, only relevant for derived
* classes that support different data types.
* @type {string}
*/
cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
/**
* Whether this input element is part of a dialog. If so, changes take effect
* in the settings UI immediately but are only actually committed when the
* user confirms the dialog. If the user cancels the dialog instead, the
* changes are rolled back in the settings UI and never committed.
* @type {boolean}
*/
cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
/**
* Whether the associated preference is controlled by a source other than the
* user's setting (can be 'policy', 'extension', 'recommended' or unset).
* @type {string}
*/
cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefCheckbox class:
// Define a constructor that uses an input element as its underlying element.
var PrefCheckbox = cr.ui.define('input');
PrefCheckbox.prototype = {
// Set up the prototype chain
__proto__: PrefInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
PrefInputElement.prototype.decorate.call(this);
this.type = 'checkbox';
},
/**
* Update the associated pref when when the user makes changes to the
* checkbox state.
* @private
*/
updatePrefFromState_: function() {
var value = this.inverted_pref ? !this.checked : this.checked;
Preferences.setBooleanPref(this.pref, value,
!this.dialogPref, this.metric);
},
/**
* Update the checkbox state when the associated pref changes.
* @param {Event} event Pref change event.
* @private
*/
updateStateFromPref_: function(event) {
var value = Boolean(event.value.value);
this.checked = this.inverted_pref ? !value : value;
},
};
/**
* Whether the mapping between checkbox state and associated pref is inverted.
* @type {boolean}
*/
cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefNumber class:
// Define a constructor that uses an input element as its underlying element.
var PrefNumber = cr.ui.define('input');
PrefNumber.prototype = {
// Set up the prototype chain
__proto__: PrefInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
PrefInputElement.prototype.decorate.call(this);
this.type = 'number';
},
/**
* Update the associated pref when when the user inputs a number.
* @private
*/
updatePrefFromState_: function() {
if (this.validity.valid) {
Preferences.setIntegerPref(this.pref, this.value,
!this.dialogPref, this.metric);
}
},
};
/////////////////////////////////////////////////////////////////////////////
// PrefRadio class:
//Define a constructor that uses an input element as its underlying element.
var PrefRadio = cr.ui.define('input');
PrefRadio.prototype = {
// Set up the prototype chain
__proto__: PrefInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
PrefInputElement.prototype.decorate.call(this);
this.type = 'radio';
},
/**
* Update the associated pref when when the user selects the radio button.
* @private
*/
updatePrefFromState_: function() {
if (this.value == 'true' || this.value == 'false') {
Preferences.setBooleanPref(this.pref,
this.value == String(this.checked),
!this.dialogPref, this.metric);
} else {
Preferences.setIntegerPref(this.pref, this.value,
!this.dialogPref, this.metric);
}
},
/**
* Update the radio button state when the associated pref changes.
* @param {Event} event Pref change event.
* @private
*/
updateStateFromPref_: function(event) {
this.checked = this.value == String(event.value.value);
},
};
/////////////////////////////////////////////////////////////////////////////
// PrefRange class:
// Define a constructor that uses an input element as its underlying element.
var PrefRange = cr.ui.define('input');
PrefRange.prototype = {
// Set up the prototype chain
__proto__: PrefInputElement.prototype,
/**
* The map from slider position to corresponding pref value.
*/
valueMap: undefined,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
PrefInputElement.prototype.decorate.call(this);
this.type = 'range';
// Listen for user events.
// TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
// fixed.
// https://bugs.webkit.org/show_bug.cgi?id=52256
this.addEventListener('keyup', this.handleRelease_.bind(this));
this.addEventListener('mouseup', this.handleRelease_.bind(this));
},
/**
* Update the associated pref when when the user releases the slider.
* @private
*/
updatePrefFromState_: function() {
Preferences.setIntegerPref(this.pref, this.mapPositionToPref(this.value),
!this.dialogPref, this.metric);
},
/**
* Ignore changes to the slider position made by the user while the slider
* has not been released.
* @private
*/
handleChange_: function() {
},
/**
* Handle changes to the slider position made by the user when the slider is
* released. If a custom change handler does not suppress it, a default
* handler is invoked that updates the associated pref.
* @param {Event} event Change event.
* @private
*/
handleRelease_: function(event) {
if (!this.customChangeHandler(event))
this.updatePrefFromState_();
},
/**
* Update the slider position when the associated pref changes.
* @param {Event} event Pref change event.
* @private
*/
updateStateFromPref_: function(event) {
var value = event.value.value;
this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
},
/**
* Map slider position to the range of values provided by the client,
* represented by |valueMap|.
* @param {number} position The slider position to map.
*/
mapPositionToPref: function(position) {
return this.valueMap ? this.valueMap[position] : position;
},
};
/////////////////////////////////////////////////////////////////////////////
// PrefSelect class:
// Define a constructor that uses a select element as its underlying element.
var PrefSelect = cr.ui.define('select');
PrefSelect.prototype = {
// Set up the prototype chain
__proto__: PrefInputElement.prototype,
/**
* Update the associated pref when when the user selects an item.
* @private
*/
updatePrefFromState_: function() {
var value = this.options[this.selectedIndex].value;
switch (this.dataType) {
case 'number':
Preferences.setIntegerPref(this.pref, value,
!this.dialogPref, this.metric);
break;
case 'double':
Preferences.setDoublePref(this.pref, value,
!this.dialogPref, this.metric);
break;
case 'boolean':
Preferences.setBooleanPref(this.pref, value == 'true',
!this.dialogPref, this.metric);
break;
case 'string':
Preferences.setStringPref(this.pref, value,
!this.dialogPref, this.metric);
break;
default:
console.error('Unknown data type for <select> UI element: ' +
this.dataType);
}
},
/**
* Update the selected item when the associated pref changes.
* @param {Event} event Pref change event.
* @private
*/
updateStateFromPref_: function(event) {
// Make sure the value is a string, because the value is stored as a
// string in the HTMLOptionElement.
value = String(event.value.value);
var found = false;
for (var i = 0; i < this.options.length; i++) {
if (this.options[i].value == value) {
this.selectedIndex = i;
found = true;
}
}
// Item not found, select first item.
if (!found)
this.selectedIndex = 0;
// The "onchange" event automatically fires when the user makes a manual
// change. It should never be fired for a programmatic change. However,
// these two lines were here already and it is hard to tell who may be
// relying on them.
if (this.onchange)
this.onchange(event);
},
};
/////////////////////////////////////////////////////////////////////////////
// PrefTextField class:
// Define a constructor that uses an input element as its underlying element.
var PrefTextField = cr.ui.define('input');
PrefTextField.prototype = {
// Set up the prototype chain
__proto__: PrefInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
PrefInputElement.prototype.decorate.call(this);
var self = this;
// Listen for user events.
window.addEventListener('unload', function() {
if (document.activeElement == self)
self.blur();
});
},
/**
* Update the associated pref when when the user inputs text.
* @private
*/
updatePrefFromState_: function(event) {
switch (this.dataType) {
case 'number':
Preferences.setIntegerPref(this.pref, this.value,
!this.dialogPref, this.metric);
break;
case 'double':
Preferences.setDoublePref(this.pref, this.value,
!this.dialogPref, this.metric);
break;
case 'url':
Preferences.setURLPref(this.pref, this.value,
!this.dialogPref, this.metric);
break;
default:
Preferences.setStringPref(this.pref, this.value,
!this.dialogPref, this.metric);
break;
}
},
};
/////////////////////////////////////////////////////////////////////////////
// PrefButton class:
// Define a constructor that uses a button element as its underlying element.
var PrefButton = cr.ui.define('button');
PrefButton.prototype = {
// Set up the prototype chain
__proto__: HTMLButtonElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
var self = this;
// Listen for pref changes.
// This element behaves like a normal button and does not affect the
// underlying preference; it just becomes disabled when the preference is
// managed, and its value is false. This is useful for buttons that should
// be disabled when the underlying Boolean preference is set to false by a
// policy or extension.
Preferences.getInstance().addEventListener(this.pref, function(event) {
updateDisabledState_(self, 'notUserModifiable',
event.value.disabled && !event.value.value);
self.controlledBy = event.value.controlledBy;
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The name of the associated preference.
* @type {string}
*/
cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the associated preference is controlled by a source other than the
* user's setting (can be 'policy', 'extension', 'recommended' or unset).
* @type {string}
*/
cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
// Export
return {
PrefCheckbox: PrefCheckbox,
PrefNumber: PrefNumber,
PrefRadio: PrefRadio,
PrefRange: PrefRange,
PrefSelect: PrefSelect,
PrefTextField: PrefTextField,
PrefButton: PrefButton
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Base class for dialogs that require saving preferences on
* confirm and resetting preference inputs on cancel.
*/
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/**
* Base class for settings dialogs.
* @constructor
* @param {string} name See OptionsPage constructor.
* @param {string} title See OptionsPage constructor.
* @param {string} pageDivName See OptionsPage constructor.
* @param {HTMLInputElement} okButton The confirmation button element.
* @param {HTMLInputElement} cancelButton The cancellation button element.
* @extends {OptionsPage}
*/
function SettingsDialog(name, title, pageDivName, okButton, cancelButton) {
OptionsPage.call(this, name, title, pageDivName);
this.okButton = okButton;
this.cancelButton = cancelButton;
}
SettingsDialog.prototype = {
__proto__: OptionsPage.prototype,
/** @override */
initializePage: function() {
this.okButton.onclick = this.handleConfirm.bind(this);
this.cancelButton.onclick = this.handleCancel.bind(this);
},
/**
* Handles the confirm button by saving the dialog preferences.
*/
handleConfirm: function() {
OptionsPage.closeOverlay();
var prefs = Preferences.getInstance();
var els = this.pageDiv.querySelectorAll('[dialog-pref]');
for (var i = 0; i < els.length; i++) {
if (els[i].pref)
prefs.commitPref(els[i].pref, els[i].metric);
}
},
/**
* Handles the cancel button by closing the overlay.
*/
handleCancel: function() {
OptionsPage.closeOverlay();
var prefs = Preferences.getInstance();
var els = this.pageDiv.querySelectorAll('[dialog-pref]');
for (var i = 0; i < els.length; i++) {
if (els[i].pref)
prefs.rollbackPref(els[i].pref);
}
},
};
return {
SettingsDialog: SettingsDialog
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* AlertOverlay class
* Encapsulated handling of a generic alert.
* @class
*/
function AlertOverlay() {
OptionsPage.call(this, 'alertOverlay', '', 'alertOverlay');
}
cr.addSingletonGetter(AlertOverlay);
AlertOverlay.prototype = {
// Inherit AlertOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Whether the page can be shown. Used to make sure the page is only
* shown via AlertOverlay.Show(), and not via the address bar.
* @private
*/
canShow_: false,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('alertOverlayOk').onclick = function(event) {
self.handleOK_();
};
$('alertOverlayCancel').onclick = function(event) {
self.handleCancel_();
};
},
/** @override */
get nestingLevel() {
// AlertOverlay is special in that it is not tied to one page or overlay.
// Set the nesting level arbitrarily high so as to always be recognized as
// the top-most visible page.
return 99;
},
/**
* Handle the 'ok' button. Clear the overlay and call the ok callback if
* available.
* @private
*/
handleOK_: function() {
OptionsPage.closeOverlay();
if (this.okCallback != undefined) {
this.okCallback.call();
}
},
/**
* Handle the 'cancel' button. Clear the overlay and call the cancel
* callback if available.
* @private
*/
handleCancel_: function() {
OptionsPage.closeOverlay();
if (this.cancelCallback != undefined) {
this.cancelCallback.call();
}
},
/**
* The page is getting hidden. Don't let it be shown again.
*/
willHidePage: function() {
canShow_ = false;
},
/** @override */
canShowPage: function() {
return this.canShow_;
},
};
/**
* Show an alert overlay with the given message, button titles, and
* callbacks.
* @param {string} title The alert title to display to the user.
* @param {string} message The alert message to display to the user.
* @param {string} okTitle The title of the OK button. If undefined or empty,
* no button is shown.
* @param {string} cancelTitle The title of the cancel button. If undefined or
* empty, no button is shown.
* @param {function} okCallback A function to be called when the user presses
* the ok button. The alert window will be closed automatically. Can be
* undefined.
* @param {function} cancelCallback A function to be called when the user
* presses the cancel button. The alert window will be closed
* automatically. Can be undefined.
*/
AlertOverlay.show = function(
title, message, okTitle, cancelTitle, okCallback, cancelCallback) {
if (title != undefined) {
$('alertOverlayTitle').textContent = title;
$('alertOverlayTitle').style.display = 'block';
} else {
$('alertOverlayTitle').style.display = 'none';
}
if (message != undefined) {
$('alertOverlayMessage').textContent = message;
$('alertOverlayMessage').style.display = 'block';
} else {
$('alertOverlayMessage').style.display = 'none';
}
if (okTitle != undefined && okTitle != '') {
$('alertOverlayOk').textContent = okTitle;
$('alertOverlayOk').style.display = 'block';
} else {
$('alertOverlayOk').style.display = 'none';
}
if (cancelTitle != undefined && cancelTitle != '') {
$('alertOverlayCancel').textContent = cancelTitle;
$('alertOverlayCancel').style.display = 'inline';
} else {
$('alertOverlayCancel').style.display = 'none';
}
var alertOverlay = AlertOverlay.getInstance();
alertOverlay.okCallback = okCallback;
alertOverlay.cancelCallback = cancelCallback;
alertOverlay.canShow_ = true;
// Intentionally don't show the URL in the location bar as we don't want
// people trying to navigate here by hand.
OptionsPage.showPageByName('alertOverlay', false);
};
// Export
return {
AlertOverlay: AlertOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
// The GUID of the loaded address.
var guid;
/**
* AutofillEditAddressOverlay class
* Encapsulated handling of the 'Add Page' overlay page.
* @class
*/
function AutofillEditAddressOverlay() {
OptionsPage.call(this, 'autofillEditAddress',
loadTimeData.getString('autofillEditAddressTitle'),
'autofill-edit-address-overlay');
}
cr.addSingletonGetter(AutofillEditAddressOverlay);
AutofillEditAddressOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.createMultiValueLists_();
var self = this;
$('autofill-edit-address-cancel-button').onclick = function(event) {
self.dismissOverlay_();
};
// TODO(jhawkins): Investigate other possible solutions.
$('autofill-edit-address-apply-button').onclick = function(event) {
// Blur active element to ensure that pending changes are committed.
if (document.activeElement)
document.activeElement.blur();
// Blurring is delayed for list elements. Queue save and close to
// ensure that pending changes have been applied.
setTimeout(function() {
self.saveAddress_();
self.dismissOverlay_();
}, 0);
};
// Prevent 'blur' events on the OK and cancel buttons, which can trigger
// insertion of new placeholder elements. The addition of placeholders
// affects layout, which interferes with being able to click on the
// buttons.
$('autofill-edit-address-apply-button').onmousedown = function(event) {
event.preventDefault();
};
$('autofill-edit-address-cancel-button').onmousedown = function(event) {
event.preventDefault();
};
self.guid = '';
self.populateCountryList_();
self.clearInputFields_();
self.connectInputEvents_();
},
/**
* Creates, decorates and initializes the multi-value lists for full name,
* phone, and email.
* @private
*/
createMultiValueLists_: function() {
var list = $('full-name-list');
options.autofillOptions.AutofillNameValuesList.decorate(list);
list.autoExpands = true;
list = $('phone-list');
options.autofillOptions.AutofillPhoneValuesList.decorate(list);
list.autoExpands = true;
list = $('email-list');
options.autofillOptions.AutofillValuesList.decorate(list);
list.autoExpands = true;
},
/**
* Updates the data model for the list named |listName| with the values from
* |entries|.
* @param {String} listName The id of the list.
* @param {Array} entries The list of items to be added to the list.
*/
setMultiValueList_: function(listName, entries) {
// Add data entries.
var list = $(listName);
// Add special entry for adding new values.
var augmentedList = entries.slice();
augmentedList.push(null);
list.dataModel = new ArrayDataModel(augmentedList);
// Update the status of the 'OK' button.
this.inputFieldChanged_();
list.dataModel.addEventListener('splice',
this.inputFieldChanged_.bind(this));
list.dataModel.addEventListener('change',
this.inputFieldChanged_.bind(this));
},
/**
* Clears any uncommitted input, resets the stored GUID and dismisses the
* overlay.
* @private
*/
dismissOverlay_: function() {
this.clearInputFields_();
this.guid = '';
OptionsPage.closeOverlay();
},
/**
* Aggregates the values in the input fields into an array and sends the
* array to the Autofill handler.
* @private
*/
saveAddress_: function() {
var address = new Array();
address[0] = this.guid;
var list = $('full-name-list');
address[1] = list.dataModel.slice(0, list.dataModel.length - 1);
address[2] = $('company-name').value;
address[3] = $('addr-line-1').value;
address[4] = $('addr-line-2').value;
address[5] = $('city').value;
address[6] = $('state').value;
address[7] = $('postal-code').value;
address[8] = $('country').value;
list = $('phone-list');
address[9] = list.dataModel.slice(0, list.dataModel.length - 1);
list = $('email-list');
address[10] = list.dataModel.slice(0, list.dataModel.length - 1);
chrome.send('setAddress', address);
},
/**
* Connects each input field to the inputFieldChanged_() method that enables
* or disables the 'Ok' button based on whether all the fields are empty or
* not.
* @private
*/
connectInputEvents_: function() {
var self = this;
$('company-name').oninput = $('addr-line-1').oninput =
$('addr-line-2').oninput = $('city').oninput = $('state').oninput =
$('postal-code').oninput = function(event) {
self.inputFieldChanged_();
};
$('country').onchange = function(event) {
self.countryChanged_();
};
},
/**
* Checks the values of each of the input fields and disables the 'Ok'
* button if all of the fields are empty.
* @private
*/
inputFieldChanged_: function() {
// Length of lists are tested for <= 1 due to the "add" placeholder item
// in the list.
var disabled =
$('full-name-list').items.length <= 1 &&
!$('company-name').value &&
!$('addr-line-1').value && !$('addr-line-2').value &&
!$('city').value && !$('state').value && !$('postal-code').value &&
!$('country').value && $('phone-list').items.length <= 1 &&
$('email-list').items.length <= 1;
$('autofill-edit-address-apply-button').disabled = disabled;
},
/**
* Updates the postal code and state field labels appropriately for the
* selected country.
* @private
*/
countryChanged_: function() {
var countryCode = $('country').value ||
loadTimeData.getString('defaultCountryCode');
var details = loadTimeData.getValue('autofillCountryData')[countryCode];
var postal = $('postal-code-label');
postal.textContent = details.postalCodeLabel;
$('state-label').textContent = details.stateLabel;
// Also update the 'Ok' button as needed.
this.inputFieldChanged_();
},
/**
* Populates the country <select> list.
* @private
*/
populateCountryList_: function() {
var countryData = loadTimeData.getValue('autofillCountryData');
var defaultCountryCode = loadTimeData.getString('defaultCountryCode');
// Build an array of the country names and their corresponding country
// codes, so that we can sort and insert them in order.
var countries = [];
for (var countryCode in countryData) {
var country = {
countryCode: countryCode,
name: countryData[countryCode].name
};
countries.push(country);
}
// Sort the countries in alphabetical order by name.
countries = countries.sort(function(a, b) {
return a.name < b.name ? -1 : 1;
});
// Insert the empty and default countries at the beginning of the array.
var emptyCountry = {
countryCode: '',
name: ''
};
var defaultCountry = {
countryCode: defaultCountryCode,
name: countryData[defaultCountryCode].name
};
var separator = {
countryCode: '',
name: '---',
disabled: true
};
countries.unshift(emptyCountry, defaultCountry, separator);
// Add the countries to the country <select> list.
var countryList = $('country');
for (var i = 0; i < countries.length; i++) {
var country = new Option(countries[i].name, countries[i].countryCode);
country.disabled = countries[i].disabled;
countryList.appendChild(country);
}
},
/**
* Clears the value of each input field.
* @private
*/
clearInputFields_: function() {
this.setMultiValueList_('full-name-list', []);
$('company-name').value = '';
$('addr-line-1').value = '';
$('addr-line-2').value = '';
$('city').value = '';
$('state').value = '';
$('postal-code').value = '';
$('country').value = '';
this.setMultiValueList_('phone-list', []);
this.setMultiValueList_('email-list', []);
this.countryChanged_();
},
/**
* Loads the address data from |address|, sets the input fields based on
* this data and stores the GUID of the address.
* @private
*/
loadAddress_: function(address) {
this.setInputFields_(address);
this.inputFieldChanged_();
this.guid = address.guid;
},
/**
* Sets the value of each input field according to |address|
* @private
*/
setInputFields_: function(address) {
this.setMultiValueList_('full-name-list', address.fullName);
$('company-name').value = address.companyName;
$('addr-line-1').value = address.addrLine1;
$('addr-line-2').value = address.addrLine2;
$('city').value = address.city;
$('state').value = address.state;
$('postal-code').value = address.postalCode;
$('country').value = address.country;
this.setMultiValueList_('phone-list', address.phone);
this.setMultiValueList_('email-list', address.email);
this.countryChanged_();
},
};
AutofillEditAddressOverlay.clearInputFields = function() {
AutofillEditAddressOverlay.getInstance().clearInputFields_();
};
AutofillEditAddressOverlay.loadAddress = function(address) {
AutofillEditAddressOverlay.getInstance().loadAddress_(address);
};
AutofillEditAddressOverlay.setTitle = function(title) {
$('autofill-address-title').textContent = title;
};
AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list',
numbers);
};
// Export
return {
AutofillEditAddressOverlay: AutofillEditAddressOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
// The GUID of the loaded credit card.
var guid_;
/**
* AutofillEditCreditCardOverlay class
* Encapsulated handling of the 'Add Page' overlay page.
* @class
*/
function AutofillEditCreditCardOverlay() {
OptionsPage.call(this, 'autofillEditCreditCard',
loadTimeData.getString('autofillEditCreditCardTitle'),
'autofill-edit-credit-card-overlay');
}
cr.addSingletonGetter(AutofillEditCreditCardOverlay);
AutofillEditCreditCardOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('autofill-edit-credit-card-cancel-button').onclick = function(event) {
self.dismissOverlay_();
};
$('autofill-edit-credit-card-apply-button').onclick = function(event) {
self.saveCreditCard_();
self.dismissOverlay_();
};
self.guid_ = '';
self.hasEditedNumber_ = false;
self.clearInputFields_();
self.connectInputEvents_();
self.setDefaultSelectOptions_();
},
/**
* Clears any uncommitted input, and dismisses the overlay.
* @private
*/
dismissOverlay_: function() {
this.clearInputFields_();
this.guid_ = '';
this.hasEditedNumber_ = false;
OptionsPage.closeOverlay();
},
/**
* Aggregates the values in the input fields into an array and sends the
* array to the Autofill handler.
* @private
*/
saveCreditCard_: function() {
var creditCard = new Array(5);
creditCard[0] = this.guid_;
creditCard[1] = $('name-on-card').value;
creditCard[2] = $('credit-card-number').value;
creditCard[3] = $('expiration-month').value;
creditCard[4] = $('expiration-year').value;
chrome.send('setCreditCard', creditCard);
},
/**
* Connects each input field to the inputFieldChanged_() method that enables
* or disables the 'Ok' button based on whether all the fields are empty or
* not.
* @private
*/
connectInputEvents_: function() {
var ccNumber = $('credit-card-number');
$('name-on-card').oninput = ccNumber.oninput =
$('expiration-month').onchange = $('expiration-year').onchange =
this.inputFieldChanged_.bind(this);
},
/**
* Checks the values of each of the input fields and disables the 'Ok'
* button if all of the fields are empty.
* @param {Event} opt_event Optional data for the 'input' event.
* @private
*/
inputFieldChanged_: function(opt_event) {
var disabled = !$('name-on-card').value && !$('credit-card-number').value;
$('autofill-edit-credit-card-apply-button').disabled = disabled;
},
/**
* Sets the default values of the options in the 'Expiration date' select
* controls.
* @private
*/
setDefaultSelectOptions_: function() {
// Set the 'Expiration month' default options.
var expirationMonth = $('expiration-month');
expirationMonth.options.length = 0;
for (var i = 1; i <= 12; ++i) {
var text;
if (i < 10)
text = '0' + i;
else
text = i;
var option = document.createElement('option');
option.text = text;
option.value = text;
expirationMonth.add(option, null);
}
// Set the 'Expiration year' default options.
var expirationYear = $('expiration-year');
expirationYear.options.length = 0;
var date = new Date();
var year = parseInt(date.getFullYear());
for (var i = 0; i < 10; ++i) {
var text = year + i;
var option = document.createElement('option');
option.text = text;
option.value = text;
expirationYear.add(option, null);
}
},
/**
* Clears the value of each input field.
* @private
*/
clearInputFields_: function() {
$('name-on-card').value = '';
$('credit-card-number').value = '';
$('expiration-month').selectedIndex = 0;
$('expiration-year').selectedIndex = 0;
// Reset the enabled status of the 'Ok' button.
this.inputFieldChanged_();
},
/**
* Sets the value of each input field according to |creditCard|
* @private
*/
setInputFields_: function(creditCard) {
$('name-on-card').value = creditCard.nameOnCard;
$('credit-card-number').value = creditCard.creditCardNumber;
// The options for the year select control may be out-dated at this point,
// e.g. the user opened the options page before midnight on New Year's Eve
// and then loaded a credit card profile to edit in the new year, so
// reload the select options just to be safe.
this.setDefaultSelectOptions_();
var idx = parseInt(creditCard.expirationMonth, 10);
$('expiration-month').selectedIndex = idx - 1;
expYear = creditCard.expirationYear;
var date = new Date();
var year = parseInt(date.getFullYear());
for (var i = 0; i < 10; ++i) {
var text = year + i;
if (expYear == String(text))
$('expiration-year').selectedIndex = i;
}
},
/**
* Loads the credit card data from |creditCard|, sets the input fields based
* on this data and stores the GUID of the credit card.
* @private
*/
loadCreditCard_: function(creditCard) {
this.setInputFields_(creditCard);
this.inputFieldChanged_();
this.guid_ = creditCard.guid;
},
};
AutofillEditCreditCardOverlay.clearInputFields = function(title) {
AutofillEditCreditCardOverlay.getInstance().clearInputFields_();
};
AutofillEditCreditCardOverlay.loadCreditCard = function(creditCard) {
AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard);
};
AutofillEditCreditCardOverlay.setTitle = function(title) {
$('autofill-credit-card-title').textContent = title;
};
// Export
return {
AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.autofillOptions', function() {
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
function AutofillEditProfileButton(guid, edit) {
var editButtonEl = document.createElement('button');
editButtonEl.className = 'list-inline-button custom-appearance';
editButtonEl.textContent =
loadTimeData.getString('autofillEditProfileButton');
editButtonEl.onclick = function(e) { edit(guid); };
// Don't select the row when clicking the button.
editButtonEl.onmousedown = function(e) {
e.stopPropagation();
};
return editButtonEl;
}
/**
* Creates a new address list item.
* @param {Array} entry An array of the form [guid, label].
* @constructor
* @extends {options.DeletableItem}
*/
function AddressListItem(entry) {
var el = cr.doc.createElement('div');
el.guid = entry[0];
el.label = entry[1];
el.__proto__ = AddressListItem.prototype;
el.decorate();
return el;
}
AddressListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The stored label.
var label = this.ownerDocument.createElement('div');
label.className = 'autofill-list-item';
label.textContent = this.label;
this.contentElement.appendChild(label);
// The 'Edit' button.
var editButtonEl = new AutofillEditProfileButton(
this.guid,
AutofillOptions.loadAddressEditor);
this.contentElement.appendChild(editButtonEl);
},
};
/**
* Creates a new credit card list item.
* @param {Array} entry An array of the form [guid, label, icon].
* @constructor
* @extends {options.DeletableItem}
*/
function CreditCardListItem(entry) {
var el = cr.doc.createElement('div');
el.guid = entry[0];
el.label = entry[1];
el.icon = entry[2];
el.description = entry[3];
el.__proto__ = CreditCardListItem.prototype;
el.decorate();
return el;
}
CreditCardListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The stored label.
var label = this.ownerDocument.createElement('div');
label.className = 'autofill-list-item';
label.textContent = this.label;
this.contentElement.appendChild(label);
// The credit card icon.
var icon = this.ownerDocument.createElement('image');
icon.src = this.icon;
icon.alt = this.description;
this.contentElement.appendChild(icon);
// The 'Edit' button.
var editButtonEl = new AutofillEditProfileButton(
this.guid,
AutofillOptions.loadCreditCardEditor);
this.contentElement.appendChild(editButtonEl);
},
};
/**
* Creates a new value list item.
* @param {AutofillValuesList} list The parent list of this item.
* @param {String} entry A string value.
* @constructor
* @extends {options.InlineEditableItem}
*/
function ValuesListItem(list, entry) {
var el = cr.doc.createElement('div');
el.list = list;
el.value = entry ? entry : '';
el.__proto__ = ValuesListItem.prototype;
el.decorate();
return el;
}
ValuesListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
// Note: This must be set prior to calling |createEditableTextCell|.
this.isPlaceholder = !this.value;
// The stored value.
var cell = this.createEditableTextCell(this.value);
this.contentElement.appendChild(cell);
this.input = cell.querySelector('input');
if (this.isPlaceholder) {
this.input.placeholder = this.list.getAttribute('placeholder');
this.deletable = false;
}
this.addEventListener('commitedit', this.onEditCommitted_);
},
/**
* @return {string} This item's value.
* @protected
*/
value_: function() {
return this.input.value;
},
/**
* @param {Object} value The value to test.
* @return {boolean} True if the given value is non-empty.
* @protected
*/
valueIsNonEmpty_: function(value) {
return !!value;
},
/**
* @return {boolean} True if value1 is logically equal to value2.
*/
valuesAreEqual_: function(value1, value2) {
return value1 === value2;
},
/**
* Clears the item's value.
* @protected
*/
clearValue_: function() {
this.input.value = '';
},
/**
* Called when committing an edit.
* If this is an "Add ..." item, committing a non-empty value adds that
* value to the end of the values list, but also leaves this "Add ..." item
* in place.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var value = this.value_();
var i = this.list.items.indexOf(this);
if (i < this.list.dataModel.length &&
this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
return;
}
var entries = this.list.dataModel.slice();
if (this.valueIsNonEmpty_(value) &&
!entries.some(this.valuesAreEqual_.bind(this, value))) {
// Update with new value.
if (this.isPlaceholder) {
// It is important that updateIndex is done before validateAndSave.
// Otherwise we can not be sure about AddRow index.
this.list.dataModel.updateIndex(i);
this.list.validateAndSave(i, 0, value);
} else {
this.list.validateAndSave(i, 1, value);
}
} else {
// Reject empty values and duplicates.
if (!this.isPlaceholder)
this.list.dataModel.splice(i, 1);
else
this.clearValue_();
}
},
};
/**
* Creates a new name value list item.
* @param {AutofillNameValuesList} list The parent list of this item.
* @param {array} entry An array of [first, middle, last] names.
* @constructor
* @extends {options.ValuesListItem}
*/
function NameListItem(list, entry) {
var el = cr.doc.createElement('div');
el.list = list;
el.first = entry ? entry[0] : '';
el.middle = entry ? entry[1] : '';
el.last = entry ? entry[2] : '';
el.__proto__ = NameListItem.prototype;
el.decorate();
return el;
}
NameListItem.prototype = {
__proto__: ValuesListItem.prototype,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
// Note: This must be set prior to calling |createEditableTextCell|.
this.isPlaceholder = !this.first && !this.middle && !this.last;
// The stored value.
// For the simulated static "input element" to display correctly, the
// value must not be empty. We use a space to force the UI to render
// correctly when the value is logically empty.
var cell = this.createEditableTextCell(this.first);
this.contentElement.appendChild(cell);
this.firstNameInput = cell.querySelector('input');
cell = this.createEditableTextCell(this.middle);
this.contentElement.appendChild(cell);
this.middleNameInput = cell.querySelector('input');
cell = this.createEditableTextCell(this.last);
this.contentElement.appendChild(cell);
this.lastNameInput = cell.querySelector('input');
if (this.isPlaceholder) {
this.firstNameInput.placeholder =
loadTimeData.getString('autofillAddFirstNamePlaceholder');
this.middleNameInput.placeholder =
loadTimeData.getString('autofillAddMiddleNamePlaceholder');
this.lastNameInput.placeholder =
loadTimeData.getString('autofillAddLastNamePlaceholder');
this.deletable = false;
}
this.addEventListener('commitedit', this.onEditCommitted_);
},
/** @override */
value_: function() {
return [this.firstNameInput.value,
this.middleNameInput.value,
this.lastNameInput.value];
},
/** @override */
valueIsNonEmpty_: function(value) {
return value[0] || value[1] || value[2];
},
/** @override */
valuesAreEqual_: function(value1, value2) {
// First, check for null values.
if (!value1 || !value2)
return value1 == value2;
return value1[0] === value2[0] &&
value1[1] === value2[1] &&
value1[2] === value2[2];
},
/** @override */
clearValue_: function() {
this.firstNameInput.value = '';
this.middleNameInput.value = '';
this.lastNameInput.value = '';
},
};
/**
* Base class for shared implementation between address and credit card lists.
* @constructor
* @extends {options.DeletableItemList}
*/
var AutofillProfileList = cr.ui.define('list');
AutofillProfileList.prototype = {
__proto__: DeletableItemList.prototype,
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.addEventListener('blur', this.onBlur_);
},
/**
* When the list loses focus, unselect all items in the list.
* @private
*/
onBlur_: function() {
this.selectionModel.unselectAll();
},
};
/**
* Create a new address list.
* @constructor
* @extends {options.AutofillProfileList}
*/
var AutofillAddressList = cr.ui.define('list');
AutofillAddressList.prototype = {
__proto__: AutofillProfileList.prototype,
decorate: function() {
AutofillProfileList.prototype.decorate.call(this);
},
/** @override */
activateItemAtIndex: function(index) {
AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
},
/** @override */
createItem: function(entry) {
return new AddressListItem(entry);
},
/** @override */
deleteItemAtIndex: function(index) {
AutofillOptions.removeData(this.dataModel.item(index)[0]);
},
};
/**
* Create a new credit card list.
* @constructor
* @extends {options.DeletableItemList}
*/
var AutofillCreditCardList = cr.ui.define('list');
AutofillCreditCardList.prototype = {
__proto__: AutofillProfileList.prototype,
decorate: function() {
AutofillProfileList.prototype.decorate.call(this);
},
/** @override */
activateItemAtIndex: function(index) {
AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
},
/** @override */
createItem: function(entry) {
return new CreditCardListItem(entry);
},
/** @override */
deleteItemAtIndex: function(index) {
AutofillOptions.removeData(this.dataModel.item(index)[0]);
},
};
/**
* Create a new value list.
* @constructor
* @extends {options.InlineEditableItemList}
*/
var AutofillValuesList = cr.ui.define('list');
AutofillValuesList.prototype = {
__proto__: InlineEditableItemList.prototype,
/** @override */
createItem: function(entry) {
return new ValuesListItem(this, entry);
},
/** @override */
deleteItemAtIndex: function(index) {
this.dataModel.splice(index, 1);
},
/** @override */
shouldFocusPlaceholder: function() {
return false;
},
/**
* Called when the list hierarchy as a whole loses or gains focus.
* If the list was focused in response to a mouse click, call into the
* superclass's implementation. If the list was focused in response to a
* keyboard navigation, focus the first item.
* If the list loses focus, unselect all the elements.
* @param {Event} e The change event.
* @private
*/
handleListFocusChange_: function(e) {
// We check to see whether there is a selected item as a proxy for
// distinguishing between mouse- and keyboard-originated focus events.
var selectedItem = this.selectedItem;
if (selectedItem)
InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
if (!e.newValue) {
// When the list loses focus, unselect all the elements.
this.selectionModel.unselectAll();
} else {
// When the list gains focus, select the first item if nothing else is
// selected.
var firstItem = this.getListItemByIndex(0);
if (!selectedItem && firstItem && e.newValue)
firstItem.handleFocus_();
}
},
/**
* Called when a new list item should be validated; subclasses are
* responsible for implementing if validation is required.
* @param {number} index The index of the item that was inserted or changed.
* @param {number} remove The number items to remove.
* @param {string} value The value of the item to insert.
*/
validateAndSave: function(index, remove, value) {
this.dataModel.splice(index, remove, value);
},
};
/**
* Create a new value list for phone number validation.
* @constructor
* @extends {options.AutofillValuesList}
*/
var AutofillNameValuesList = cr.ui.define('list');
AutofillNameValuesList.prototype = {
__proto__: AutofillValuesList.prototype,
/** @override */
createItem: function(entry) {
return new NameListItem(this, entry);
},
};
/**
* Create a new value list for phone number validation.
* @constructor
* @extends {options.AutofillValuesList}
*/
var AutofillPhoneValuesList = cr.ui.define('list');
AutofillPhoneValuesList.prototype = {
__proto__: AutofillValuesList.prototype,
/** @override */
validateAndSave: function(index, remove, value) {
var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
numbers.splice(index, remove, value);
var info = new Array();
info[0] = index;
info[1] = numbers;
info[2] = $('country').value;
chrome.send('validatePhoneNumbers', info);
},
};
return {
AddressListItem: AddressListItem,
CreditCardListItem: CreditCardListItem,
ValuesListItem: ValuesListItem,
NameListItem: NameListItem,
AutofillAddressList: AutofillAddressList,
AutofillCreditCardList: AutofillCreditCardList,
AutofillValuesList: AutofillValuesList,
AutofillNameValuesList: AutofillNameValuesList,
AutofillPhoneValuesList: AutofillPhoneValuesList,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var ArrayDataModel = cr.ui.ArrayDataModel;
/////////////////////////////////////////////////////////////////////////////
// AutofillOptions class:
/**
* Encapsulated handling of Autofill options page.
* @constructor
*/
function AutofillOptions() {
OptionsPage.call(this,
'autofill',
loadTimeData.getString('autofillOptionsPageTabTitle'),
'autofill-options');
}
cr.addSingletonGetter(AutofillOptions);
AutofillOptions.prototype = {
__proto__: OptionsPage.prototype,
/**
* The address list.
* @type {DeletableItemList}
* @private
*/
addressList_: null,
/**
* The credit card list.
* @type {DeletableItemList}
* @private
*/
creditCardList_: null,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.createAddressList_();
this.createCreditCardList_();
var self = this;
$('autofill-add-address').onclick = function(event) {
self.showAddAddressOverlay_();
};
$('autofill-add-creditcard').onclick = function(event) {
self.showAddCreditCardOverlay_();
};
$('autofill-options-confirm').onclick = function(event) {
OptionsPage.closeOverlay();
};
// TODO(jhawkins): What happens when Autofill is disabled whilst on the
// Autofill options page?
},
/**
* Creates, decorates and initializes the address list.
* @private
*/
createAddressList_: function() {
this.addressList_ = $('address-list');
options.autofillOptions.AutofillAddressList.decorate(this.addressList_);
this.addressList_.autoExpands = true;
},
/**
* Creates, decorates and initializes the credit card list.
* @private
*/
createCreditCardList_: function() {
this.creditCardList_ = $('creditcard-list');
options.autofillOptions.AutofillCreditCardList.decorate(
this.creditCardList_);
this.creditCardList_.autoExpands = true;
},
/**
* Shows the 'Add address' overlay, specifically by loading the
* 'Edit address' overlay, emptying the input fields and modifying the
* overlay title.
* @private
*/
showAddAddressOverlay_: function() {
var title = loadTimeData.getString('addAddressTitle');
AutofillEditAddressOverlay.setTitle(title);
AutofillEditAddressOverlay.clearInputFields();
OptionsPage.navigateToPage('autofillEditAddress');
},
/**
* Shows the 'Add credit card' overlay, specifically by loading the
* 'Edit credit card' overlay, emptying the input fields and modifying the
* overlay title.
* @private
*/
showAddCreditCardOverlay_: function() {
var title = loadTimeData.getString('addCreditCardTitle');
AutofillEditCreditCardOverlay.setTitle(title);
AutofillEditCreditCardOverlay.clearInputFields();
OptionsPage.navigateToPage('autofillEditCreditCard');
},
/**
* Updates the data model for the address list with the values from
* |entries|.
* @param {Array} entries The list of addresses.
*/
setAddressList_: function(entries) {
this.addressList_.dataModel = new ArrayDataModel(entries);
},
/**
* Updates the data model for the credit card list with the values from
* |entries|.
* @param {Array} entries The list of credit cards.
*/
setCreditCardList_: function(entries) {
this.creditCardList_.dataModel = new ArrayDataModel(entries);
},
/**
* Removes the Autofill address or credit card represented by |guid|.
* @param {String} guid The GUID of the address to remove.
* @private
*/
removeData_: function(guid) {
chrome.send('removeData', [guid]);
},
/**
* Requests profile data for the address represented by |guid| from the
* PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
* calls showEditAddressOverlay().
* @param {String} guid The GUID of the address to edit.
* @private
*/
loadAddressEditor_: function(guid) {
chrome.send('loadAddressEditor', [guid]);
},
/**
* Requests profile data for the credit card represented by |guid| from the
* PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
* calls showEditCreditCardOverlay().
* @param {String} guid The GUID of the credit card to edit.
* @private
*/
loadCreditCardEditor_: function(guid) {
chrome.send('loadCreditCardEditor', [guid]);
},
/**
* Shows the 'Edit address' overlay, using the data in |address| to fill the
* input fields. |address| is a list with one item, an associative array
* that contains the address data.
* @private
*/
showEditAddressOverlay_: function(address) {
var title = loadTimeData.getString('editAddressTitle');
AutofillEditAddressOverlay.setTitle(title);
AutofillEditAddressOverlay.loadAddress(address);
OptionsPage.navigateToPage('autofillEditAddress');
},
/**
* Shows the 'Edit credit card' overlay, using the data in |credit_card| to
* fill the input fields. |address| is a list with one item, an associative
* array that contains the credit card data.
* @private
*/
showEditCreditCardOverlay_: function(creditCard) {
var title = loadTimeData.getString('editCreditCardTitle');
AutofillEditCreditCardOverlay.setTitle(title);
AutofillEditCreditCardOverlay.loadCreditCard(creditCard);
OptionsPage.navigateToPage('autofillEditCreditCard');
},
};
AutofillOptions.setAddressList = function(entries) {
AutofillOptions.getInstance().setAddressList_(entries);
};
AutofillOptions.setCreditCardList = function(entries) {
AutofillOptions.getInstance().setCreditCardList_(entries);
};
AutofillOptions.removeData = function(guid) {
AutofillOptions.getInstance().removeData_(guid);
};
AutofillOptions.loadAddressEditor = function(guid) {
AutofillOptions.getInstance().loadAddressEditor_(guid);
};
AutofillOptions.loadCreditCardEditor = function(guid) {
AutofillOptions.getInstance().loadCreditCardEditor_(guid);
};
AutofillOptions.editAddress = function(address) {
AutofillOptions.getInstance().showEditAddressOverlay_(address);
};
AutofillOptions.editCreditCard = function(creditCard) {
AutofillOptions.getInstance().showEditCreditCardOverlay_(creditCard);
};
// Export
return {
AutofillOptions: AutofillOptions
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var ArrayDataModel = cr.ui.ArrayDataModel;
var RepeatingButton = cr.ui.RepeatingButton;
//
// BrowserOptions class
// Encapsulated handling of browser options page.
//
function BrowserOptions() {
OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
'settings');
}
cr.addSingletonGetter(BrowserOptions);
BrowserOptions.prototype = {
__proto__: options.OptionsPage.prototype,
// State variables.
syncSetupCompleted: false,
/**
* Keeps track of whether |onShowHomeButtonChanged_| has been called. See
* |onShowHomeButtonChanged_|.
* @type {bool}
* @private
*/
onShowHomeButtonChangedCalled_: false,
/**
* Track if page initialization is complete. All C++ UI handlers have the
* chance to manipulate page content within their InitializePage mathods.
* This flag is set to true after all initializers have been called.
* @type (boolean}
* @private
*/
initializationComplete_: false,
/** @override */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
// Ensure that navigation events are unblocked on uber page. A reload of
// the settings page while an overlay is open would otherwise leave uber
// page in a blocked state, where tab switching is not possible.
uber.invokeMethodOnParent('stopInterceptingEvents');
window.addEventListener('message', this.handleWindowMessage_.bind(this));
$('advanced-settings-expander').onclick = function() {
self.toggleSectionWithAnimation_(
$('advanced-settings'),
$('advanced-settings-container'));
// If the link was focused (i.e., it was activated using the keyboard)
// and it was used to show the section (rather than hiding it), focus
// the first element in the container.
if (document.activeElement === $('advanced-settings-expander') &&
$('advanced-settings').style.height === '') {
var focusElement = $('advanced-settings-container').querySelector(
'button, input, list, select, a[href]');
if (focusElement)
focusElement.focus();
}
}
$('advanced-settings').addEventListener('webkitTransitionEnd',
this.updateAdvancedSettingsExpander_.bind(this));
if (cr.isChromeOS)
UIAccountTweaks.applyGuestModeVisibility(document);
// Sync (Sign in) section.
this.updateSyncState_(loadTimeData.getValue('syncData'));
$('start-stop-sync').onclick = function(event) {
if (self.syncSetupCompleted)
SyncSetupOverlay.showStopSyncingUI();
else if (cr.isChromeOS)
SyncSetupOverlay.showSetupUIWithoutLogin();
else
SyncSetupOverlay.showSetupUI();
};
$('customize-sync').onclick = function(event) {
if (cr.isChromeOS)
SyncSetupOverlay.showSetupUIWithoutLogin();
else
SyncSetupOverlay.showSetupUI();
};
// Internet connection section (ChromeOS only).
if (cr.isChromeOS) {
options.network.NetworkList.decorate($('network-list'));
options.network.NetworkList.refreshNetworkData(
loadTimeData.getValue('networkData'));
}
// On Startup section.
Preferences.getInstance().addEventListener('session.restore_on_startup',
this.onRestoreOnStartupChanged_.bind(this));
Preferences.getInstance().addEventListener(
'session.urls_to_restore_on_startup',
function(event) {
$('startup-set-pages').disabled = event.value.disabled;
});
$('startup-set-pages').onclick = function() {
OptionsPage.navigateToPage('startup');
};
// Appearance section.
Preferences.getInstance().addEventListener('browser.show_home_button',
this.onShowHomeButtonChanged_.bind(this));
Preferences.getInstance().addEventListener('homepage',
this.onHomePageChanged_.bind(this));
Preferences.getInstance().addEventListener('homepage_is_newtabpage',
this.onHomePageIsNtpChanged_.bind(this));
$('change-home-page').onclick = function(event) {
OptionsPage.navigateToPage('homePageOverlay');
};
if ($('set-wallpaper')) {
$('set-wallpaper').onclick = function(event) {
chrome.send('openWallpaperManager');
};
}
$('themes-gallery').onclick = function(event) {
window.open(loadTimeData.getString('themesGalleryURL'));
};
$('themes-reset').onclick = function(event) {
chrome.send('themesReset');
};
// Device section (ChromeOS only).
if (cr.isChromeOS) {
$('keyboard-settings-button').onclick = function(evt) {
OptionsPage.navigateToPage('keyboard-overlay');
};
$('pointer-settings-button').onclick = function(evt) {
OptionsPage.navigateToPage('pointer-overlay');
};
}
// Search section.
$('manage-default-search-engines').onclick = function(event) {
OptionsPage.navigateToPage('searchEngines');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageSearchEngines']);
};
$('default-search-engine').addEventListener('change',
this.setDefaultSearchEngine_);
if (loadTimeData.getValue('instant_enabled') ==
'instant_extended.enabled') {
// We don't want to see the confirm dialog for instant extended.
$('instant-enabled-control').removeAttribute('dialog-pref');
$('instant-enabled-indicator').removeAttribute('dialog-pref');
}
// Users section.
if (loadTimeData.valueExists('profilesInfo')) {
$('profiles-section').hidden = false;
var profilesList = $('profiles-list');
options.browser_options.ProfileList.decorate(profilesList);
profilesList.autoExpands = true;
this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
profilesList.addEventListener('change',
this.setProfileViewButtonsStatus_);
$('profiles-create').onclick = function(event) {
chrome.send('createProfileInfo');
};
$('profiles-manage').onclick = function(event) {
ManageProfileOverlay.showManageDialog();
};
$('profiles-delete').onclick = function(event) {
var selectedProfile = self.getSelectedProfileItem_();
if (selectedProfile)
ManageProfileOverlay.showDeleteDialog(selectedProfile);
};
}
if (cr.isChromeOS) {
if (!UIAccountTweaks.loggedInAsGuest()) {
$('account-picture-wrapper').onclick = function(event) {
OptionsPage.navigateToPage('changePicture');
};
}
// Username (canonical email) of the currently logged in user or
// |kGuestUser| if a guest session is active.
this.username_ = loadTimeData.getString('username');
this.updateAccountPicture_();
$('manage-accounts-button').onclick = function(event) {
OptionsPage.navigateToPage('accounts');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageAccounts']);
};
} else {
$('import-data').onclick = function(event) {
ImportDataOverlay.show();
chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
};
if ($('themes-GTK-button')) {
$('themes-GTK-button').onclick = function(event) {
chrome.send('themesSetGTK');
};
}
}
// Default browser section.
if (!cr.isChromeOS) {
$('set-as-default-browser').onclick = function(event) {
chrome.send('becomeDefaultBrowser');
};
$('auto-launch').onclick = this.handleAutoLaunchChanged_;
}
// Privacy section.
$('privacyContentSettingsButton').onclick = function(event) {
OptionsPage.navigateToPage('content');
OptionsPage.showTab($('cookies-nav-tab'));
chrome.send('coreOptionsUserMetricsAction',
['Options_ContentSettings']);
};
$('privacyClearDataButton').onclick = function(event) {
OptionsPage.navigateToPage('clearBrowserData');
chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
};
// 'metricsReportingEnabled' element is only present on Chrome branded
// builds.
if ($('metricsReportingEnabled')) {
$('metricsReportingEnabled').onclick = function(event) {
chrome.send('metricsReportingCheckboxAction',
[String(event.currentTarget.checked)]);
};
}
// Bluetooth (CrOS only).
if (cr.isChromeOS) {
options.system.bluetooth.BluetoothDeviceList.decorate(
$('bluetooth-paired-devices-list'));
$('bluetooth-add-device').onclick =
this.handleAddBluetoothDevice_.bind(this);
$('enable-bluetooth').onchange = function(event) {
var state = $('enable-bluetooth').checked;
chrome.send('bluetoothEnableChange', [Boolean(state)]);
};
$('bluetooth-reconnect-device').onclick = function(event) {
var device = $('bluetooth-paired-devices-list').selectedItem;
var address = device.address;
chrome.send('updateBluetoothDevice', [address, 'connect']);
OptionsPage.closeOverlay();
};
$('bluetooth-reconnect-device').onmousedown = function(event) {
// Prevent 'blur' event, which would reset the list selection,
// thereby disabling the apply button.
event.preventDefault();
};
$('bluetooth-paired-devices-list').addEventListener('change',
function() {
var item = $('bluetooth-paired-devices-list').selectedItem;
var disabled = !item || item.connected;
$('bluetooth-reconnect-device').disabled = disabled;
});
}
// Passwords and Forms section.
$('autofill-settings').onclick = function(event) {
OptionsPage.navigateToPage('autofill');
chrome.send('coreOptionsUserMetricsAction',
['Options_ShowAutofillSettings']);
};
$('manage-passwords').onclick = function(event) {
OptionsPage.navigateToPage('passwords');
OptionsPage.showTab($('passwords-nav-tab'));
chrome.send('coreOptionsUserMetricsAction',
['Options_ShowPasswordManager']);
};
if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
// Disable and turn off Autofill in guest mode.
var autofillEnabled = $('autofill-enabled');
autofillEnabled.disabled = true;
autofillEnabled.checked = false;
cr.dispatchSimpleEvent(autofillEnabled, 'change');
$('autofill-settings').disabled = true;
// Disable and turn off Password Manager in guest mode.
var passwordManagerEnabled = $('password-manager-enabled');
passwordManagerEnabled.disabled = true;
passwordManagerEnabled.checked = false;
cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
$('manage-passwords').disabled = true;
}
if (cr.isMac) {
$('mac-passwords-warning').hidden =
!loadTimeData.getBoolean('multiple_profiles');
}
// Network section.
if (!cr.isChromeOS) {
$('proxiesConfigureButton').onclick = function(event) {
chrome.send('showNetworkProxySettings');
};
}
// Web Content section.
$('fontSettingsCustomizeFontsButton').onclick = function(event) {
OptionsPage.navigateToPage('fonts');
chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
};
$('defaultFontSize').onchange = function(event) {
var value = event.target.options[event.target.selectedIndex].value;
Preferences.setIntegerPref(
'webkit.webprefs.default_fixed_font_size',
value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
chrome.send('defaultFontSizeAction', [String(value)]);
};
$('defaultZoomFactor').onchange = function(event) {
chrome.send('defaultZoomFactorAction',
[String(event.target.options[event.target.selectedIndex].value)]);
};
// Languages section.
$('language-button').onclick = function(event) {
OptionsPage.navigateToPage('languages');
chrome.send('coreOptionsUserMetricsAction',
['Options_LanuageAndSpellCheckSettings']);
};
// Downloads section.
Preferences.getInstance().addEventListener('download.default_directory',
this.onDefaultDownloadDirectoryChanged_.bind(this));
$('downloadLocationChangeButton').onclick = function(event) {
chrome.send('selectDownloadLocation');
};
if (!cr.isChromeOS) {
$('autoOpenFileTypesResetToDefault').onclick = function(event) {
chrome.send('autoOpenFileTypesAction');
};
}
// HTTPS/SSL section.
if (cr.isWindows || cr.isMac) {
$('certificatesManageButton').onclick = function(event) {
chrome.send('showManageSSLCertificates');
};
} else {
$('certificatesManageButton').onclick = function(event) {
OptionsPage.navigateToPage('certificates');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageSSLCertificates']);
};
}
// Cloud Print section.
// 'cloudPrintProxyEnabled' is true for Chrome branded builds on
// certain platforms, or could be enabled by a lab.
if (!cr.isChromeOS) {
$('cloudPrintConnectorSetupButton').onclick = function(event) {
if ($('cloudPrintManageButton').style.display == 'none') {
// Disable the button, set its text to the intermediate state.
$('cloudPrintConnectorSetupButton').textContent =
loadTimeData.getString('cloudPrintConnectorEnablingButton');
$('cloudPrintConnectorSetupButton').disabled = true;
chrome.send('showCloudPrintSetupDialog');
} else {
chrome.send('disableCloudPrintConnector');
}
};
}
$('cloudPrintManageButton').onclick = function(event) {
chrome.send('showCloudPrintManagePage');
};
// Accessibility section (CrOS only).
if (cr.isChromeOS) {
$('accessibility-spoken-feedback-check').onchange = function(event) {
chrome.send('spokenFeedbackChange',
[$('accessibility-spoken-feedback-check').checked]);
};
$('accessibility-high-contrast-check').onchange = function(event) {
chrome.send('highContrastChange',
[$('accessibility-high-contrast-check').checked]);
};
}
// Display management section (CrOS only).
if (cr.isChromeOS) {
$('display-options').onclick = function(event) {
OptionsPage.navigateToPage('display');
chrome.send('coreOptionsUserMetricsAction',
['Options_Display']);
};
}
// Factory reset section (CrOS only).
if (cr.isChromeOS) {
$('factory-reset-restart').onclick = function(event) {
OptionsPage.navigateToPage('factoryResetData');
};
}
},
/** @override */
didShowPage: function() {
$('search-field').focus();
},
/**
* Called after all C++ UI handlers have called InitializePage to notify
* that initialization is complete.
* @private
*/
notifyInitializationComplete_: function() {
this.initializationComplete_ = true;
cr.dispatchSimpleEvent(document, 'initializationComplete');
},
/**
* Event listener for the 'session.restore_on_startup' pref.
* @param {Event} event The preference change event.
* @private
*/
onRestoreOnStartupChanged_: function(event) {
/** @const */ var showHomePageValue = 0;
if (event.value.value == showHomePageValue) {
// If the user previously selected "Show the homepage", the
// preference will already be migrated to "Open a specific page". So
// the only way to reach this code is if the 'restore on startup'
// preference is managed.
assert(event.value.controlledBy);
// Select "open the following pages" and lock down the list of URLs
// to reflect the intention of the policy.
$('startup-show-pages').checked = true;
StartupOverlay.getInstance().setControlsDisabled(true);
} else {
// Re-enable the controls in the startup overlay if necessary.
StartupOverlay.getInstance().updateControlStates();
}
},
/**
* Handler for messages sent from the main uber page.
* @param {Event} e The 'message' event from the uber page.
* @private
*/
handleWindowMessage_: function(e) {
if (e.data.method == 'frameSelected')
$('search-field').focus();
},
/**
* Shows the given section.
* @param {HTMLElement} section The section to be shown.
* @param {HTMLElement} container The container for the section. Must be
* inside of |section|.
* @param {boolean} animate Indicate if the expansion should be animated.
* @private
*/
showSection_: function(section, container, animate) {
if (animate)
this.addTransitionEndListener_(section);
// Unhide
section.hidden = false;
var expander = function() {
// Reveal the section using a WebKit transition if animating.
if (animate) {
section.classList.add('sliding');
section.style.height = container.offsetHeight + 'px';
} else {
section.style.height = 'auto';
}
// Force an update of the list of paired Bluetooth devices.
if (cr.isChromeOS)
$('bluetooth-paired-devices-list').refresh();
};
// Delay starting the transition if animating so that hidden change will
// be processed.
if (animate)
setTimeout(expander, 0);
else
expander();
},
/**
* Shows the given section, with animation.
* @param {HTMLElement} section The section to be shown.
* @param {HTMLElement} container The container for the section. Must be
* inside of |section|.
* @private
*/
showSectionWithAnimation_: function(section, container) {
this.showSection_(section, container, /*animate */ true);
},
/**
* See showSectionWithAnimation_.
*/
hideSectionWithAnimation_: function(section, container) {
this.addTransitionEndListener_(section);
// Before we start hiding the section, we need to set
// the height to a pixel value.
section.style.height = container.offsetHeight + 'px';
// Delay starting the transition so that the height change will be
// processed.
setTimeout(function() {
// Hide the section using a WebKit transition.
section.classList.add('sliding');
section.style.height = '';
}, 0);
},
/**
* See showSectionWithAnimation_.
*/
toggleSectionWithAnimation_: function(section, container) {
if (section.style.height == '')
this.showSectionWithAnimation_(section, container);
else
this.hideSectionWithAnimation_(section, container);
},
/**
* Scrolls the settings page to make the section visible auto-expanding
* advanced settings if required. The transition is not animated. This
* method is used to ensure that a section associated with an overlay
* is visible when the overlay is closed.
* @param {!Element} section The section to make visible.
* @private
*/
scrollToSection_: function(section) {
var advancedSettings = $('advanced-settings');
var container = $('advanced-settings-container');
if (advancedSettings.hidden && section.parentNode == container) {
this.showSection_($('advanced-settings'),
$('advanced-settings-container'),
/* animate */ false);
this.updateAdvancedSettingsExpander_();
}
if (!this.initializationComplete_) {
var self = this;
var callback = function() {
document.removeEventListener('initializationComplete', callback);
self.scrollToSection_(section);
};
document.addEventListener('initializationComplete', callback);
return;
}
var pageContainer = $('page-container');
var pageTop = parseFloat(pageContainer.style.top);
var topSection = document.querySelector('#page-container section');
var pageHeight = document.body.scrollHeight - topSection.offsetTop;
var sectionTop = section.offsetTop;
var sectionHeight = section.offsetHeight;
var marginBottom = window.getComputedStyle(section).marginBottom;
if (marginBottom)
sectionHeight += parseFloat(marginBottom);
if (pageHeight - pageTop < sectionTop + sectionHeight) {
pageContainer.oldScrollTop = sectionTop + sectionHeight - pageHeight;
var verticalPosition = pageContainer.getBoundingClientRect().top -
pageContainer.oldScrollTop;
pageContainer.style.top = verticalPosition + 'px';
}
},
/**
* Adds a |webkitTransitionEnd| listener to the given section so that
* it can be animated. The listener will only be added to a given section
* once, so this can be called as multiple times.
* @param {HTMLElement} section The section to be animated.
* @private
*/
addTransitionEndListener_: function(section) {
if (section.hasTransitionEndListener_)
return;
section.addEventListener('webkitTransitionEnd',
this.onTransitionEnd_.bind(this));
section.hasTransitionEndListener_ = true;
},
/**
* Called after an animation transition has ended.
* @private
*/
onTransitionEnd_: function(event) {
if (event.propertyName != 'height')
return;
var section = event.target;
// Disable WebKit transitions.
section.classList.remove('sliding');
if (section.style.height == '') {
// Hide the content so it can't get tab focus.
section.hidden = true;
} else {
// Set the section height to 'auto' to allow for size changes
// (due to font change or dynamic content).
section.style.height = 'auto';
}
},
updateAdvancedSettingsExpander_: function() {
var expander = $('advanced-settings-expander');
if ($('advanced-settings').style.height == '')
expander.textContent = loadTimeData.getString('showAdvancedSettings');
else
expander.textContent = loadTimeData.getString('hideAdvancedSettings');
},
/**
* Updates the sync section with the given state.
* @param {Object} syncData A bunch of data records that describe the status
* of the sync system.
* @private
*/
updateSyncState_: function(syncData) {
if (!syncData.syncSystemEnabled) {
$('sync-section').hidden = true;
return;
}
$('sync-section').hidden = false;
this.syncSetupCompleted = syncData.setupCompleted;
$('customize-sync').hidden = !syncData.setupCompleted;
var startStopButton = $('start-stop-sync');
startStopButton.disabled = syncData.managed ||
syncData.setupInProgress;
startStopButton.hidden =
syncData.setupCompleted && cr.isChromeOS;
startStopButton.textContent =
syncData.setupCompleted ?
loadTimeData.getString('syncButtonTextStop') :
syncData.setupInProgress ?
loadTimeData.getString('syncButtonTextInProgress') :
loadTimeData.getString('syncButtonTextStart');
$('start-stop-sync-indicator').hidden = startStopButton.hidden;
// TODO(estade): can this just be textContent?
$('sync-status-text').innerHTML = syncData.statusText;
var statusSet = syncData.statusText.length != 0;
$('sync-overview').hidden = statusSet;
$('sync-status').hidden = !statusSet;
$('sync-action-link').textContent = syncData.actionLinkText;
$('sync-action-link').hidden = syncData.actionLinkText.length == 0;
$('sync-action-link').disabled = syncData.managed;
// On Chrome OS, sign out the user and sign in again to get fresh
// credentials on auth errors.
$('sync-action-link').onclick = function(event) {
if (cr.isChromeOS && syncData.hasError)
SyncSetupOverlay.doSignOutOnAuthError();
else
SyncSetupOverlay.showErrorUI();
};
if (syncData.hasError)
$('sync-status').classList.add('sync-error');
else
$('sync-status').classList.remove('sync-error');
$('customize-sync').disabled = syncData.hasUnrecoverableError;
// Move #enable-auto-login-checkbox to a different location on CrOS.
if (cr.isChromeOs) {
$('sync-general').insertBefore($('sync-status').nextSibling,
$('enable-auto-login-checkbox'));
}
$('enable-auto-login-checkbox').hidden = !syncData.autoLoginVisible;
},
/**
* Get the start/stop sync button DOM element. Used for testing.
* @return {DOMElement} The start/stop sync button.
* @private
*/
getStartStopSyncButton_: function() {
return $('start-stop-sync');
},
/**
* Event listener for the 'show home button' preference. Shows/hides the
* UI for changing the home page with animation, unless this is the first
* time this function is called, in which case there is no animation.
* @param {Event} event The preference change event.
*/
onShowHomeButtonChanged_: function(event) {
var section = $('change-home-page-section');
if (this.onShowHomeButtonChangedCalled_) {
var container = $('change-home-page-section-container');
if (event.value.value)
this.showSectionWithAnimation_(section, container);
else
this.hideSectionWithAnimation_(section, container);
} else {
section.hidden = !event.value.value;
this.onShowHomeButtonChangedCalled_ = true;
}
},
/**
* Event listener for the 'homepage is NTP' preference. Updates the label
* next to the 'Change' button.
* @param {Event} event The preference change event.
*/
onHomePageIsNtpChanged_: function(event) {
if (!event.value.uncommitted) {
$('home-page-url').hidden = event.value.value;
$('home-page-ntp').hidden = !event.value.value;
}
},
/**
* Event listener for changes to the homepage preference. Updates the label
* next to the 'Change' button.
* @param {Event} event The preference change event.
*/
onHomePageChanged_: function(event) {
if (!event.value.uncommitted)
$('home-page-url').textContent = this.stripHttp_(event.value.value);
},
/**
* Removes the 'http://' from a URL, like the omnibox does. If the string
* doesn't start with 'http://' it is returned unchanged.
* @param {string} url The url to be processed
* @return {string} The url with the 'http://' removed.
*/
stripHttp_: function(url) {
return url.replace(/^http:\/\//, '');
},
/**
* Shows the autoLaunch preference and initializes its checkbox value.
* @param {bool} enabled Whether autolaunch is enabled or or not.
* @private
*/
updateAutoLaunchState_: function(enabled) {
$('auto-launch-option').hidden = false;
$('auto-launch').checked = enabled;
},
/**
* Called when the value of the download.default_directory preference
* changes.
* @param {Event} event Change event.
* @private
*/
onDefaultDownloadDirectoryChanged_: function(event) {
$('downloadLocationPath').value = event.value.value;
if (cr.isChromeOS) {
// On ChromeOS, replace /special/drive with Drive for drive paths, and
// /home/chronos/user/Downloads with Downloads for local files.
// Also replace '/' with ' \u203a ' (angled quote sign) everywhere.
var path = $('downloadLocationPath').value;
path = path.replace(/^\/special\/drive/, 'Google Drive');
path = path.replace(/^\/home\/chronos\/user\//, '');
path = path.replace(/\//g, ' \u203a ');
$('downloadLocationPath').value = path;
}
if (event.value.disabled)
$('download-location-label').classList.add('disabled');
else
$('download-location-label').classList.remove('disabled');
$('downloadLocationChangeButton').disabled = event.value.disabled;
},
/**
* Update the Default Browsers section based on the current state.
* @param {string} statusString Description of the current default state.
* @param {boolean} isDefault Whether or not the browser is currently
* default.
* @param {boolean} canBeDefault Whether or not the browser can be default.
* @private
*/
updateDefaultBrowserState_: function(statusString, isDefault,
canBeDefault) {
if (!cr.isChromeOS) {
var label = $('default-browser-state');
label.textContent = statusString;
$('set-as-default-browser').hidden = !canBeDefault || isDefault;
}
},
/**
* Clears the search engine popup.
* @private
*/
clearSearchEngines_: function() {
$('default-search-engine').textContent = '';
},
/**
* Updates the search engine popup with the given entries.
* @param {Array} engines List of available search engines.
* @param {number} defaultValue The value of the current default engine.
* @param {boolean} defaultManaged Whether the default search provider is
* managed. If true, the default search provider can't be changed.
* @private
*/
updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
this.clearSearchEngines_();
engineSelect = $('default-search-engine');
engineSelect.disabled = defaultManaged;
if (defaultManaged && defaultValue == -1)
return;
engineCount = engines.length;
var defaultIndex = -1;
for (var i = 0; i < engineCount; i++) {
var engine = engines[i];
var option = new Option(engine.name, engine.index);
if (defaultValue == option.value)
defaultIndex = i;
engineSelect.appendChild(option);
}
if (defaultIndex >= 0)
engineSelect.selectedIndex = defaultIndex;
},
/**
* Set the default search engine based on the popup selection.
* @private
*/
setDefaultSearchEngine_: function() {
var engineSelect = $('default-search-engine');
var selectedIndex = engineSelect.selectedIndex;
if (selectedIndex >= 0) {
var selection = engineSelect.options[selectedIndex];
chrome.send('setDefaultSearchEngine', [String(selection.value)]);
}
},
/**
* Sets or clear whether Chrome should Auto-launch on computer startup.
* @private
*/
handleAutoLaunchChanged_: function() {
chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
},
/**
* Get the selected profile item from the profile list. This also works
* correctly if the list is not displayed.
* @return {Object} the profile item object, or null if nothing is selected.
* @private
*/
getSelectedProfileItem_: function() {
var profilesList = $('profiles-list');
if (profilesList.hidden) {
if (profilesList.dataModel.length > 0)
return profilesList.dataModel.item(0);
} else {
return profilesList.selectedItem;
}
return null;
},
/**
* Helper function to set the status of profile view buttons to disabled or
* enabled, depending on the number of profiles and selection status of the
* profiles list.
* @private
*/
setProfileViewButtonsStatus_: function() {
var profilesList = $('profiles-list');
var selectedProfile = profilesList.selectedItem;
var hasSelection = selectedProfile != null;
var hasSingleProfile = profilesList.dataModel.length == 1;
$('profiles-manage').disabled = !hasSelection ||
!selectedProfile.isCurrentProfile;
if (hasSelection && !selectedProfile.isCurrentProfile)
$('profiles-manage').title = loadTimeData.getString('currentUserOnly');
else
$('profiles-manage').title = '';
$('profiles-delete').disabled = !hasSelection && !hasSingleProfile;
var importData = $('import-data');
if (importData) {
importData.disabled = $('import-data').disabled = hasSelection &&
!selectedProfile.isCurrentProfile;
}
},
/**
* Display the correct dialog layout, depending on how many profiles are
* available.
* @param {number} numProfiles The number of profiles to display.
* @private
*/
setProfileViewSingle_: function(numProfiles) {
var hasSingleProfile = numProfiles == 1;
$('profiles-list').hidden = hasSingleProfile;
$('profiles-single-message').hidden = !hasSingleProfile;
$('profiles-manage').hidden = hasSingleProfile;
$('profiles-delete').textContent = hasSingleProfile ?
loadTimeData.getString('profilesDeleteSingle') :
loadTimeData.getString('profilesDelete');
},
/**
* Adds all |profiles| to the list.
* @param {Array.<Object>} profiles An array of profile info objects.
* each object is of the form:
* profileInfo = {
* name: "Profile Name",
* iconURL: "chrome://path/to/icon/image",
* filePath: "/path/to/profile/data/on/disk",
* isCurrentProfile: false
* };
* @private
*/
setProfilesInfo_: function(profiles) {
this.setProfileViewSingle_(profiles.length);
// add it to the list, even if the list is hidden so we can access it
// later.
$('profiles-list').dataModel = new ArrayDataModel(profiles);
// Received new data. If showing the "manage" overlay, keep it up to
// date. If showing the "delete" overlay, close it.
if (ManageProfileOverlay.getInstance().visible &&
!$('manage-profile-overlay-manage').hidden) {
ManageProfileOverlay.showManageDialog();
} else {
ManageProfileOverlay.getInstance().visible = false;
}
this.setProfileViewButtonsStatus_();
},
/**
* Returns the currently active profile for this browser window.
* @return {Object} A profile info object.
* @private
*/
getCurrentProfile_: function() {
for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
var profile = $('profiles-list').dataModel.item(i);
if (profile.isCurrentProfile)
return profile;
}
assert(false,
'There should always be a current profile, but none found.');
},
setGtkThemeButtonEnabled_: function(enabled) {
if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i))
$('themes-GTK-button').disabled = !enabled;
},
setThemesResetButtonEnabled_: function(enabled) {
$('themes-reset').disabled = !enabled;
},
/**
* (Re)loads IMG element with current user account picture.
* @private
*/
updateAccountPicture_: function() {
var picture = $('account-picture');
if (picture) {
picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
Date.now();
}
},
/**
* Handle the 'add device' button click.
* @private
*/
handleAddBluetoothDevice_: function() {
$('bluetooth-unpaired-devices-list').clear();
chrome.send('findBluetoothDevices');
OptionsPage.showPageByName('bluetooth', false);
},
/**
* Enables factory reset section.
* @private
*/
enableFactoryResetSection_: function() {
$('factory-reset-section').hidden = false;
},
/**
* Set the checked state of the metrics reporting checkbox.
* @private
*/
setMetricsReportingCheckboxState_: function(checked, disabled) {
$('metricsReportingEnabled').checked = checked;
$('metricsReportingEnabled').disabled = disabled;
},
/**
* @private
*/
setMetricsReportingSettingVisibility_: function(visible) {
if (visible)
$('metricsReportingSetting').style.display = 'block';
else
$('metricsReportingSetting').style.display = 'none';
},
/**
* Set the visibility of the password generation checkbox.
* @private
*/
setPasswordGenerationSettingVisibility_: function(visible) {
if (visible)
$('password-generation-checkbox').style.display = 'block';
else
$('password-generation-checkbox').style.display = 'none';
},
/**
* Set the font size selected item.
* @private
*/
setFontSize_: function(font_size_value) {
var selectCtl = $('defaultFontSize');
for (var i = 0; i < selectCtl.options.length; i++) {
if (selectCtl.options[i].value == font_size_value) {
selectCtl.selectedIndex = i;
if ($('Custom'))
selectCtl.remove($('Custom').index);
return;
}
}
// Add/Select Custom Option in the font size label list.
if (!$('Custom')) {
var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
-1, false, true);
option.setAttribute('id', 'Custom');
selectCtl.add(option);
}
$('Custom').selected = true;
},
/**
* Populate the page zoom selector with values received from the caller.
* @param {Array} items An array of items to populate the selector.
* each object is an array with three elements as follows:
* 0: The title of the item (string).
* 1: The value of the item (number).
* 2: Whether the item should be selected (boolean).
* @private
*/
setupPageZoomSelector_: function(items) {
var element = $('defaultZoomFactor');
// Remove any existing content.
element.textContent = '';
// Insert new child nodes into select element.
var value, title, selected;
for (var i = 0; i < items.length; i++) {
title = items[i][0];
value = items[i][1];
selected = items[i][2];
element.appendChild(new Option(title, value, false, selected));
}
},
/**
* Shows/hides the autoOpenFileTypesResetToDefault button and label, with
* animation.
* @param {boolean} display Whether to show the button and label or not.
* @private
*/
setAutoOpenFileTypesDisplayed_: function(display) {
if (cr.isChromeOS)
return;
if ($('advanced-settings').hidden) {
// If the Advanced section is hidden, don't animate the transition.
$('auto-open-file-types-section').hidden = !display;
} else {
if (display) {
this.showSectionWithAnimation_(
$('auto-open-file-types-section'),
$('auto-open-file-types-container'));
} else {
this.hideSectionWithAnimation_(
$('auto-open-file-types-section'),
$('auto-open-file-types-container'));
}
}
},
/**
* Set the enabled state for the proxy settings button.
* @private
*/
setupProxySettingsSection_: function(disabled, label) {
if (!cr.isChromeOS) {
$('proxiesConfigureButton').disabled = disabled;
$('proxiesLabel').textContent = label;
}
},
/**
* Set the Cloud Print proxy UI to enabled, disabled, or processing.
* @private
*/
setupCloudPrintConnectorSection_: function(disabled, label, allowed) {
if (!cr.isChromeOS) {
$('cloudPrintConnectorLabel').textContent = label;
if (disabled || !allowed) {
$('cloudPrintConnectorSetupButton').textContent =
loadTimeData.getString('cloudPrintConnectorDisabledButton');
$('cloudPrintManageButton').style.display = 'none';
} else {
$('cloudPrintConnectorSetupButton').textContent =
loadTimeData.getString('cloudPrintConnectorEnabledButton');
$('cloudPrintManageButton').style.display = 'inline';
}
$('cloudPrintConnectorSetupButton').disabled = !allowed;
}
},
/**
* @private
*/
removeCloudPrintConnectorSection_: function() {
if (!cr.isChromeOS) {
var connectorSectionElm = $('cloud-print-connector-section');
if (connectorSectionElm)
connectorSectionElm.parentNode.removeChild(connectorSectionElm);
}
},
/**
* Set the initial state of the spoken feedback checkbox.
* @private
*/
setSpokenFeedbackCheckboxState_: function(checked) {
$('accessibility-spoken-feedback-check').checked = checked;
},
/**
* Set the initial state of the high contrast checkbox.
* @private
*/
setHighContrastCheckboxState_: function(checked) {
$('accessibility-high-contrast-check').checked = checked;
},
/**
* Set the initial state of the virtual keyboard checkbox.
* @private
*/
setVirtualKeyboardCheckboxState_: function(checked) {
// TODO(zork): Update UI
},
/**
* Show/hide mouse settings slider.
* @private
*/
showMouseControls_: function(show) {
$('mouse-settings').hidden = !show;
},
/**
* Show/hide touchpad-related settings.
* @private
*/
showTouchpadControls_: function(show) {
$('touchpad-settings').hidden = !show;
$('accessibility-tap-dragging').hidden = !show;
},
/**
* Show/hide the display options button on the System settings page.
* @private
*/
showDisplayOptions_: function(show) {
$('display-options-section').hidden = !show;
},
/**
* Activate the Bluetooth settings section on the System settings page.
* @private
*/
showBluetoothSettings_: function() {
$('bluetooth-devices').hidden = false;
},
/**
* Dectivates the Bluetooth settings section from the System settings page.
* @private
*/
hideBluetoothSettings_: function() {
$('bluetooth-devices').hidden = true;
},
/**
* Sets the state of the checkbox indicating if Bluetooth is turned on. The
* state of the "Find devices" button and the list of discovered devices may
* also be affected by a change to the state.
* @param {boolean} checked Flag Indicating if Bluetooth is turned on.
* @private
*/
setBluetoothState_: function(checked) {
$('enable-bluetooth').checked = checked;
$('bluetooth-paired-devices-list').parentNode.hidden = !checked;
$('bluetooth-add-device').hidden = !checked;
$('bluetooth-reconnect-device').hidden = !checked;
// Flush list of previously discovered devices if bluetooth is turned off.
if (!checked) {
$('bluetooth-paired-devices-list').clear();
$('bluetooth-unpaired-devices-list').clear();
} else {
chrome.send('getPairedBluetoothDevices');
}
},
/**
* Adds an element to the list of available Bluetooth devices. If an element
* with a matching address is found, the existing element is updated.
* @param {{name: string,
* address: string,
* paired: boolean,
* bonded: boolean,
* connected: boolean}} device
* Decription of the Bluetooth device.
* @private
*/
addBluetoothDevice_: function(device) {
var list = $('bluetooth-unpaired-devices-list');
if (device.paired) {
// Test to see if the device is currently in the unpaired list, in which
// case it should be removed from that list.
var index = $('bluetooth-unpaired-devices-list').find(device.address);
if (index != undefined)
$('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
list = $('bluetooth-paired-devices-list');
} else {
// Test to see if the device is currently in the paired list, in which
// case it should be removed from that list.
var index = $('bluetooth-paired-devices-list').find(device.address);
if (index != undefined)
$('bluetooth-paired-devices-list').deleteItemAtIndex(index);
}
list.appendDevice(device);
// One device can be in the process of pairing. If found, display
// the Bluetooth pairing overlay.
if (device.pairing)
BluetoothPairing.showDialog(device);
},
/**
* Removes an element from the list of available devices.
* @param {string} address Unique address of the device.
* @private
*/
removeBluetoothDevice_: function(address) {
var index = $('bluetooth-unpaired-devices-list').find(address);
if (index != undefined) {
$('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
} else {
index = $('bluetooth-paired-devices-list').find(address);
if (index != undefined)
$('bluetooth-paired-devices-list').deleteItemAtIndex(index);
}
}
};
//Forward public APIs to private implementations.
[
'addBluetoothDevice',
'enableFactoryResetSection',
'getCurrentProfile',
'getStartStopSyncButton',
'hideBluetoothSettings',
'notifyInitializationComplete',
'removeBluetoothDevice',
'removeCloudPrintConnectorSection',
'scrollToSection',
'setAutoOpenFileTypesDisplayed',
'setBluetoothState',
'setFontSize',
'setGtkThemeButtonEnabled',
'setHighContrastCheckboxState',
'setMetricsReportingCheckboxState',
'setMetricsReportingSettingVisibility',
'setPasswordGenerationSettingVisibility',
'setProfilesInfo',
'setSpokenFeedbackCheckboxState',
'setThemesResetButtonEnabled',
'setVirtualKeyboardCheckboxState',
'setupCloudPrintConnectorSection',
'setupPageZoomSelector',
'setupProxySettingsSection',
'showBluetoothSettings',
'showDisplayOptions',
'showMouseControls',
'showTouchpadControls',
'updateAccountPicture',
'updateAutoLaunchState',
'updateDefaultBrowserState',
'updateSearchEngines',
'updateStartupPages',
'updateSyncState',
].forEach(function(name) {
BrowserOptions[name] = function() {
var instance = BrowserOptions.getInstance();
return instance[name + '_'].apply(instance, arguments);
};
});
if (cr.isChromeOS) {
/**
* Returns username (canonical email) of the user logged in (ChromeOS only).
* @return {string} user email.
*/
// TODO(jhawkins): Investigate the use case for this method.
BrowserOptions.getLoggedInUsername = function() {
return BrowserOptions.getInstance().username_;
};
}
// Export
return {
BrowserOptions: BrowserOptions
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.browser_options', function() {
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
/**
* Creates a new profile list item.
* @param {Object} profileInfo The profile this item respresents.
* @constructor
* @extends {cr.ui.DeletableItem}
*/
function ProfileListItem(profileInfo) {
var el = cr.doc.createElement('div');
el.profileInfo_ = profileInfo;
ProfileListItem.decorate(el);
return el;
}
/**
* Decorates an element as a profile list item.
* @param {!HTMLElement} el The element to decorate.
*/
ProfileListItem.decorate = function(el) {
el.__proto__ = ProfileListItem.prototype;
el.decorate();
};
ProfileListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* @type {string} the file path of this profile list item.
*/
get profilePath() {
return this.profileInfo_.filePath;
},
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
var profileInfo = this.profileInfo_;
var iconEl = this.ownerDocument.createElement('img');
iconEl.className = 'profile-img';
iconEl.src = profileInfo.iconURL;
this.contentElement.appendChild(iconEl);
var nameEl = this.ownerDocument.createElement('div');
if (profileInfo.isCurrentProfile)
nameEl.classList.add('profile-item-current');
this.contentElement.appendChild(nameEl);
var displayName = profileInfo.name;
if (profileInfo.isCurrentProfile) {
displayName = loadTimeData.getStringF('profilesListItemCurrent',
profileInfo.name);
}
nameEl.textContent = displayName;
// Ensure that the button cannot be tabbed to for accessibility reasons.
this.closeButtonElement.tabIndex = -1;
},
};
var ProfileList = cr.ui.define('list');
ProfileList.prototype = {
__proto__: DeletableItemList.prototype,
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.selectionModel = new ListSingleSelectionModel();
},
/** @override */
createItem: function(pageInfo) {
var item = new ProfileListItem(pageInfo);
return item;
},
/** @override */
deleteItemAtIndex: function(index) {
ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
},
/** @override */
activateItemAtIndex: function(index) {
// Don't allow the user to edit a profile that is not current.
var profileInfo = this.dataModel.item(index);
if (profileInfo.isCurrentProfile)
ManageProfileOverlay.showManageDialog(profileInfo);
},
};
return {
ProfileList: ProfileList
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.browser_options', function() {
/** @const */ var AutocompleteList = cr.ui.AutocompleteList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
/**
* Creates a new startup page list item.
* @param {Object} pageInfo The page this item represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function StartupPageListItem(pageInfo) {
var el = cr.doc.createElement('div');
el.pageInfo_ = pageInfo;
StartupPageListItem.decorate(el);
return el;
}
/**
* Decorates an element as a startup page list item.
* @param {!HTMLElement} el The element to decorate.
*/
StartupPageListItem.decorate = function(el) {
el.__proto__ = StartupPageListItem.prototype;
el.decorate();
};
StartupPageListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Input field for editing the page url.
* @type {HTMLElement}
* @private
*/
urlField_: null,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
var pageInfo = this.pageInfo_;
if (pageInfo.modelIndex == '-1') {
this.isPlaceholder = true;
pageInfo.title = loadTimeData.getString('startupAddLabel');
pageInfo.url = '';
}
var titleEl = this.ownerDocument.createElement('div');
titleEl.className = 'title';
titleEl.classList.add('favicon-cell');
titleEl.classList.add('weakrtl');
titleEl.textContent = pageInfo.title;
if (!this.isPlaceholder) {
titleEl.style.backgroundImage = url(getFaviconURL(pageInfo.url));
titleEl.title = pageInfo.tooltip;
}
this.contentElement.appendChild(titleEl);
var urlEl = this.createEditableTextCell(pageInfo.url);
urlEl.className = 'url';
urlEl.classList.add('weakrtl');
this.contentElement.appendChild(urlEl);
var urlField = urlEl.querySelector('input');
urlField.className = 'weakrtl';
urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder');
this.urlField_ = urlField;
this.addEventListener('commitedit', this.onEditCommitted_);
var self = this;
urlField.addEventListener('focus', function(event) {
self.parentNode.autocompleteList.attachToInput(urlField);
});
urlField.addEventListener('blur', function(event) {
self.parentNode.autocompleteList.detach();
});
if (!this.isPlaceholder)
this.draggable = true;
},
/** @override */
get currentInputIsValid() {
return this.urlField_.validity.valid;
},
/** @override */
get hasBeenEdited() {
return this.urlField_.value != this.pageInfo_.url;
},
/**
* Called when committing an edit; updates the model.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var url = this.urlField_.value;
if (this.isPlaceholder)
chrome.send('addStartupPage', [url]);
else
chrome.send('editStartupPage', [this.pageInfo_.modelIndex, url]);
},
};
var StartupPageList = cr.ui.define('list');
StartupPageList.prototype = {
__proto__: InlineEditableItemList.prototype,
/**
* An autocomplete suggestion list for URL editing.
* @type {AutocompleteList}
*/
autocompleteList: null,
/**
* The drop position information: "below" or "above".
*/
dropPos: null,
/** @override */
decorate: function() {
InlineEditableItemList.prototype.decorate.call(this);
// Listen to drag and drop events.
this.addEventListener('dragstart', this.handleDragStart_.bind(this));
this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
this.addEventListener('dragover', this.handleDragOver_.bind(this));
this.addEventListener('drop', this.handleDrop_.bind(this));
this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
this.addEventListener('dragend', this.handleDragEnd_.bind(this));
},
/** @override */
createItem: function(pageInfo) {
var item = new StartupPageListItem(pageInfo);
item.urlField_.disabled = this.disabled;
return item;
},
/** @override */
deleteItemAtIndex: function(index) {
chrome.send('removeStartupPages', [String(index)]);
},
/**
* Computes the target item of drop event.
* @param {Event} e The drop or dragover event.
* @private
*/
getTargetFromDropEvent_: function(e) {
var target = e.target;
// e.target may be an inner element of the list item
while (target != null && !(target instanceof StartupPageListItem)) {
target = target.parentNode;
}
return target;
},
/**
* Handles the dragstart event.
* @param {Event} e The dragstart event.
* @private
*/
handleDragStart_: function(e) {
// Prevent dragging if the list is disabled.
if (this.disabled) {
e.preventDefault();
return false;
}
var target = e.target;
// StartupPageListItem should be the only draggable element type in the
// page but let's make sure.
if (target instanceof StartupPageListItem) {
this.draggedItem = target;
this.draggedItem.editable = false;
e.dataTransfer.effectAllowed = 'move';
// We need to put some kind of data in the drag or it will be
// ignored. Use the URL in case the user drags to a text field or the
// desktop.
e.dataTransfer.setData('text/plain', target.urlField_.value);
}
},
/*
* Handles the dragenter event.
* @param {Event} e The dragenter event.
* @private
*/
handleDragEnter_: function(e) {
e.preventDefault();
},
/*
* Handles the dragover event.
* @param {Event} e The dragover event.
* @private
*/
handleDragOver_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
// Determines whether the drop target is to accept the drop.
// The drop is only successful on another StartupPageListItem.
if (!(dropTarget instanceof StartupPageListItem) ||
dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
this.hideDropMarker_();
return;
}
// Compute the drop postion. Should we move the dragged item to
// below or above the drop target?
var rect = dropTarget.getBoundingClientRect();
var dy = e.clientY - rect.top;
var yRatio = dy / rect.height;
var dropPos = yRatio <= .5 ? 'above' : 'below';
this.dropPos = dropPos;
this.showDropMarker_(dropTarget, dropPos);
e.preventDefault();
},
/*
* Handles the drop event.
* @param {Event} e The drop event.
* @private
*/
handleDrop_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
this.hideDropMarker_();
// Insert the selection at the new position.
var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
if (this.dropPos == 'below')
newIndex += 1;
var selected = this.selectionModel.selectedIndexes;
var stringized_selected = [];
for (var j = 0; j < selected.length; j++)
stringized_selected.push(String(selected[j]));
chrome.send('dragDropStartupPage',
[String(newIndex), stringized_selected]);
},
/**
* Handles the dragleave event.
* @param {Event} e The dragleave event.
* @private
*/
handleDragLeave_: function(e) {
this.hideDropMarker_();
},
/**
* Handles the dragend event.
* @param {Event} e The dragend event.
* @private
*/
handleDragEnd_: function(e) {
this.draggedItem.editable = true;
this.draggedItem.updateEditState();
},
/**
* Shows and positions the marker to indicate the drop target.
* @param {HTMLElement} target The current target list item of drop.
* @param {string} pos 'below' or 'above'.
* @private
*/
showDropMarker_: function(target, pos) {
window.clearTimeout(this.hideDropMarkerTimer_);
var marker = $('startupPagesListDropmarker');
var rect = target.getBoundingClientRect();
var markerHeight = 6;
if (pos == 'above') {
marker.style.top = (rect.top - markerHeight / 2) + 'px';
} else {
marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
}
marker.style.width = rect.width + 'px';
marker.style.left = rect.left + 'px';
marker.style.display = 'block';
},
/**
* Hides the drop marker.
* @private
*/
hideDropMarker_: function() {
// Hide the marker in a timeout to reduce flickering as we move between
// valid drop targets.
window.clearTimeout(this.hideDropMarkerTimer_);
this.hideDropMarkerTimer_ = window.setTimeout(function() {
$('startupPagesListDropmarker').style.display = '';
}, 100);
},
};
return {
StartupPageList: StartupPageList
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* ClearBrowserDataOverlay class
* Encapsulated handling of the 'Clear Browser Data' overlay page.
* @class
*/
function ClearBrowserDataOverlay() {
OptionsPage.call(this, 'clearBrowserData',
loadTimeData.getString('clearBrowserDataOverlayTabTitle'),
'clear-browser-data-overlay');
}
cr.addSingletonGetter(ClearBrowserDataOverlay);
ClearBrowserDataOverlay.prototype = {
// Inherit ClearBrowserDataOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
var f = this.updateCommitButtonState_.bind(this);
var types = ['browser.clear_data.browsing_history',
'browser.clear_data.download_history',
'browser.clear_data.cache',
'browser.clear_data.cookies',
'browser.clear_data.passwords',
'browser.clear_data.form_data',
'browser.clear_data.hosted_apps_data',
'browser.clear_data.content_licenses'];
types.forEach(function(type) {
Preferences.getInstance().addEventListener(type, f);
});
var checkboxes = document.querySelectorAll(
'#cbd-content-area input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].onclick = f;
}
this.updateCommitButtonState_();
$('clear-browser-data-dismiss').onclick = function(event) {
ClearBrowserDataOverlay.dismiss();
};
$('clear-browser-data-commit').onclick = function(event) {
ClearBrowserDataOverlay.setClearingState(true);
chrome.send('performClearBrowserData');
};
},
// Set the enabled state of the commit button.
updateCommitButtonState_: function() {
var checkboxes = document.querySelectorAll(
'#cbd-content-area input[type=checkbox]');
var isChecked = false;
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
isChecked = true;
break;
}
}
$('clear-browser-data-commit').disabled = !isChecked;
},
};
//
// Chrome callbacks
//
ClearBrowserDataOverlay.setClearingState = function(state) {
$('delete-browsing-history-checkbox').disabled = state;
$('delete-download-history-checkbox').disabled = state;
$('delete-cache-checkbox').disabled = state;
$('delete-cookies-checkbox').disabled = state;
$('delete-passwords-checkbox').disabled = state;
$('delete-form-data-checkbox').disabled = state;
$('delete-hosted-apps-data-checkbox').disabled = state;
$('deauthorize-content-licenses-checkbox').disabled = state;
$('clear-browser-data-time-period').disabled = state;
$('cbd-throbber').style.visibility = state ? 'visible' : 'hidden';
$('clear-browser-data-dismiss').disabled = state;
if (state)
$('clear-browser-data-commit').disabled = true;
else
ClearBrowserDataOverlay.getInstance().updateCommitButtonState_();
};
ClearBrowserDataOverlay.doneClearing = function() {
// The delay gives the user some feedback that the clearing
// actually worked. Otherwise the dialog just vanishes instantly in most
// cases.
window.setTimeout(function() {
ClearBrowserDataOverlay.dismiss();
}, 200);
};
ClearBrowserDataOverlay.dismiss = function() {
OptionsPage.closeOverlay();
this.setClearingState(false);
};
// Export
return {
ClearBrowserDataOverlay: ClearBrowserDataOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/**
* A dialog that will pop up when the user attempts to set the value of the
* Boolean |pref| to |true|, asking for confirmation. If the user clicks OK,
* the new value is committed to Chrome. If the user clicks Cancel or leaves
* the settings page, the new value is discarded.
* @constructor
* @param {string} name See OptionsPage constructor.
* @param {string} title See OptionsPage constructor.
* @param {string} pageDivName See OptionsPage constructor.
* @param {HTMLInputElement} okButton The confirmation button element.
* @param {HTMLInputElement} cancelButton The cancellation button element.
* @param {string} pref The pref that requires confirmation.
* @param {string} metric User metrics identifier.
* @param {string} confirmed_pref A pref used to remember whether the user has
* confirmed the dialog before. This ensures that the user is presented
* with the dialog only once. If left |undefined| or |null|, the dialog
* will pop up every time the user attempts to set |pref| to |true|.
* @extends {OptionsPage}
*/
function ConfirmDialog(name, title, pageDivName, okButton, cancelButton, pref,
metric, confirmed_pref) {
OptionsPage.call(this, name, title, pageDivName);
this.okButton = okButton;
this.cancelButton = cancelButton;
this.pref = pref;
this.metric = metric;
this.confirmed_pref = confirmed_pref;
this.confirmed_ = false;
}
ConfirmDialog.prototype = {
// Set up the prototype chain
__proto__: OptionsPage.prototype,
/**
* Handle changes to |pref|. Only uncommitted changes are relevant as these
* originate from user and need to be explicitly committed to take effect.
* Pop up the dialog or commit the change, depending on whether confirmation
* is needed.
* @param {Event} event Change event.
* @private
*/
onPrefChanged_: function(event) {
if (!event.value.uncommitted)
return;
if (event.value.value && !this.confirmed_)
OptionsPage.showPageByName(this.name, false);
else
Preferences.getInstance().commitPref(this.pref, this.metric);
},
/**
* Handle changes to |confirmed_pref| by caching them.
* @param {Event} event Change event.
* @private
*/
onConfirmedChanged_: function(event) {
this.confirmed_ = event.value.value;
},
/** @override */
initializePage: function() {
this.okButton.onclick = this.handleConfirm.bind(this);
this.cancelButton.onclick = this.handleCancel.bind(this);
Preferences.getInstance().addEventListener(
this.pref, this.onPrefChanged_.bind(this));
if (this.confirmed_pref) {
Preferences.getInstance().addEventListener(
this.confirmed_pref, this.onConfirmedChanged_.bind(this));
}
},
/**
* Handle the confirm button by committing the |pref| change. If
* |confirmed_pref| has been specified, also remember that the dialog has
* been confirmed to avoid bringing it up in the future.
*/
handleConfirm: function() {
OptionsPage.closeOverlay();
Preferences.getInstance().commitPref(this.pref, this.metric);
if (this.confirmed_pref)
Preferences.setBooleanPref(this.confirmed_pref, true, true);
},
/**
* Handle the cancel button by rolling back the |pref| change without it
* ever taking effect.
*/
handleCancel: function() {
OptionsPage.closeOverlay();
Preferences.getInstance().rollbackPref(this.pref);
},
};
return {
ConfirmDialog: ConfirmDialog
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
if (!loadTimeData.getBoolean('newContentSettings')) {
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
//////////////////////////////////////////////////////////////////////////////
// ContentSettings class:
/**
* Encapsulated handling of content settings page.
* @constructor
*/
function ContentSettings() {
this.activeNavTab = null;
OptionsPage.call(this, 'content',
loadTimeData.getString('contentSettingsPageTabTitle'),
'content-settings-page');
}
cr.addSingletonGetter(ContentSettings);
ContentSettings.prototype = {
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
chrome.send('getContentFilterSettings');
var exceptionsButtons =
this.pageDiv.querySelectorAll('.exceptions-list-button');
for (var i = 0; i < exceptionsButtons.length; i++) {
exceptionsButtons[i].onclick = function(event) {
var page = ContentSettingsExceptionsArea.getInstance();
// Add on the proper hash for the content type, and store that in the
// history so back/forward and tab restore works.
var hash = event.currentTarget.getAttribute('contentType');
var url = page.name + '#' + hash;
window.history.replaceState({pageName: page.name},
page.title,
'/' + url);
// Navigate after the history has been replaced in order to have the
// correct hash loaded.
OptionsPage.navigateToPage('contentExceptions');
uber.invokeMethodOnParent('setPath', {path: url});
uber.invokeMethodOnParent('setTitle',
{title: loadTimeData.getString(hash + 'TabTitle')});
};
}
var manageHandlersButton = $('manage-handlers-button');
if (manageHandlersButton) {
manageHandlersButton.onclick = function(event) {
OptionsPage.navigateToPage('handlers');
};
}
$('manage-galleries-button').onclick = function(event) {
OptionsPage.navigateToPage('manageGalleries');
};
if (cr.isChromeOS)
UIAccountTweaks.applyGuestModeVisibility(document);
// Cookies filter page ---------------------------------------------------
$('show-cookies-button').onclick = function(event) {
chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']);
OptionsPage.navigateToPage('cookies');
};
$('content-settings-overlay-confirm').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
$('media-pepper-flash-default').hidden = true;
$('media-pepper-flash-exceptions').hidden = true;
$('media-select-mic').addEventListener('change',
ContentSettings.setDefaultMicrophone_);
$('media-select-camera').addEventListener('change',
ContentSettings.setDefaultCamera_);
},
};
ContentSettings.updateHandlersEnabledRadios = function(enabled) {
var selector = '#content-settings-page input[type=radio][value=' +
(enabled ? 'allow' : 'block') + '].handler-radio';
document.querySelector(selector).checked = true;
};
/**
* Sets the values for all the content settings radios.
* @param {Object} dict A mapping from radio groups to the checked value for
* that group.
*/
ContentSettings.setContentFilterSettingsValue = function(dict) {
for (var group in dict) {
var managedBy = dict[group].managedBy;
var controlledBy = managedBy == 'policy' || managedBy == 'extension' ?
managedBy : null;
document.querySelector('input[type=radio][name=' + group + '][value=' +
dict[group].value + ']').checked = true;
var radios = document.querySelectorAll('input[type=radio][name=' +
group + ']');
for (var i = 0, len = radios.length; i < len; i++) {
radios[i].disabled = (managedBy != 'default');
radios[i].controlledBy = controlledBy;
}
var indicators = document.querySelectorAll(
'span.controlled-setting-indicator[content-setting=' + group + ']');
if (indicators.length == 0)
continue;
// Create a synthetic pref change event decorated as
// CoreOptionsHandler::CreateValueForPref() does.
var event = new cr.Event(group);
event.value = {
value: dict[group].value,
controlledBy: controlledBy,
};
for (var i = 0; i < indicators.length; i++)
indicators[i].handlePrefChange(event);
}
};
/**
* Updates the labels and indicators for the Media settings. Those require
* special handling because they are backed by multiple prefs and can change
* their scope based on the managed state of the backing prefs.
* @param {Object} mediaSettings A dictionary containing the following fields:
* {String} askText The label for the ask radio button.
* {String} blockText The label for the block radio button.
* {Boolean} cameraDisabled Whether to disable the camera dropdown.
* {Boolean} micDisabled Whether to disable the microphone dropdown.
* {Boolean} showBubble Wether to show the managed icon and bubble for the
* media label.
* {String} bubbleText The text to use inside the bubble if it is shown.
*/
ContentSettings.updateMediaUI = function(mediaSettings) {
$('media-stream-ask-label').innerHTML =
loadTimeData.getString(mediaSettings.askText);
$('media-stream-block-label').innerHTML =
loadTimeData.getString(mediaSettings.blockText);
if (mediaSettings.micDisabled)
$('media-select-mic').disabled = true;
if (mediaSettings.cameraDisabled)
$('media-select-camera').disabled = true;
OptionsPage.hideBubble();
// Create a synthetic pref change event decorated as
// CoreOptionsHandler::CreateValueForPref() does.
var event = new cr.Event();
event.value = {};
if (mediaSettings.showBubble) {
event.value = { controlledBy: 'policy' };
$('media-indicator').setAttribute(
'textpolicy', loadTimeData.getString(mediaSettings.bubbleText));
$('media-indicator').location = cr.ui.ArrowLocation.TOP_START;
}
$('media-indicator').handlePrefChange(event);
};
/**
* Initializes an exceptions list.
* @param {string} type The content type that we are setting exceptions for.
* @param {Array} list An array of pairs, where the first element of each pair
* is the filter string, and the second is the setting (allow/block).
*/
ContentSettings.setExceptions = function(type, list) {
var exceptionsList =
document.querySelector('div[contentType=' + type + ']' +
' list[mode=normal]');
exceptionsList.setExceptions(list);
};
ContentSettings.setHandlers = function(list) {
$('handlers-list').setHandlers(list);
};
ContentSettings.setIgnoredHandlers = function(list) {
$('ignored-handlers-list').setHandlers(list);
};
ContentSettings.setOTRExceptions = function(type, list) {
var exceptionsList =
document.querySelector('div[contentType=' + type + ']' +
' list[mode=otr]');
exceptionsList.parentNode.hidden = false;
exceptionsList.setExceptions(list);
};
/**
* The browser's response to a request to check the validity of a given URL
* pattern.
* @param {string} type The content type.
* @param {string} mode The browser mode.
* @param {string} pattern The pattern.
* @param {bool} valid Whether said pattern is valid in the context of
* a content exception setting.
*/
ContentSettings.patternValidityCheckComplete =
function(type, mode, pattern, valid) {
var exceptionsList =
document.querySelector('div[contentType=' + type + '] ' +
'list[mode=' + mode + ']');
exceptionsList.patternValidityCheckComplete(pattern, valid);
};
/**
* Shows/hides the link to the Pepper Flash camera and microphone default
* settings.
* Please note that whether the link is actually showed or not is also
* affected by the style class pepper-flash-settings.
*/
ContentSettings.showMediaPepperFlashDefaultLink = function(show) {
$('media-pepper-flash-default').hidden = !show;
}
/**
* Shows/hides the link to the Pepper Flash camera and microphone
* site-specific settings.
* Please note that whether the link is actually showed or not is also
* affected by the style class pepper-flash-settings.
*/
ContentSettings.showMediaPepperFlashExceptionsLink = function(show) {
$('media-pepper-flash-exceptions').hidden = !show;
}
/**
* Updates the microphone/camera devices menu with the given entries.
* @param {string} type The device type.
* @param {Array} devices List of available devices.
* @param {string} defaultdevice The unique id of the current default device.
*/
ContentSettings.updateDevicesMenu = function(type, devices, defaultdevice) {
var deviceSelect = '';
if (type == 'mic') {
deviceSelect = $('media-select-mic');
} else if (type == 'camera') {
deviceSelect = $('media-select-camera');
} else {
console.error('Unknown device type for <device select> UI element: ' +
type);
return;
}
deviceSelect.textContent = '';
var deviceCount = devices.length;
var defaultIndex = -1;
for (var i = 0; i < deviceCount; i++) {
var device = devices[i];
var option = new Option(device.name, device.id);
if (option.value == defaultdevice)
defaultIndex = i;
deviceSelect.appendChild(option);
}
if (defaultIndex >= 0)
deviceSelect.selectedIndex = defaultIndex;
};
/**
* Set the default microphone device based on the popup selection.
* @private
*/
ContentSettings.setDefaultMicrophone_ = function() {
var deviceSelect = $('media-select-mic');
chrome.send('setDefaultCaptureDevice', ['mic', deviceSelect.value]);
};
/**
* Set the default camera device based on the popup selection.
* @private
*/
ContentSettings.setDefaultCamera_ = function() {
var deviceSelect = $('media-select-camera');
chrome.send('setDefaultCaptureDevice', ['camera', deviceSelect.value]);
};
// Export
return {
ContentSettings: ContentSettings
};
});
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
if (loadTimeData.getBoolean('newContentSettings')) {
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
//////////////////////////////////////////////////////////////////////////////
// ContentSettings class:
/**
* Encapsulated handling of content settings page.
* @constructor
*/
function ContentSettings() {
this.activeNavTab = null;
OptionsPage.call(this, 'content',
loadTimeData.getString('contentSettingsPageTabTitle'),
'content-settings-page2');
}
cr.addSingletonGetter(ContentSettings);
ContentSettings.prototype = {
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
$('content-settings-overlay-confirm2').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
},
};
ContentSettings.updateHandlersEnabledRadios = function(enabled) {
// Not implemented.
};
/**
* Sets the values for all the content settings radios.
* @param {Object} dict A mapping from radio groups to the checked value for
* that group.
*/
ContentSettings.setContentFilterSettingsValue = function(dict) {
// Not implemented.
};
/**
* Initializes an exceptions list.
* @param {string} type The content type that we are setting exceptions for.
* @param {Array} list An array of pairs, where the first element of each pair
* is the filter string, and the second is the setting (allow/block).
*/
ContentSettings.setExceptions = function(type, list) {
// Not implemented.
};
ContentSettings.setHandlers = function(list) {
// Not implemented.
};
ContentSettings.setIgnoredHandlers = function(list) {
// Not implemented.
};
ContentSettings.setOTRExceptions = function(type, list) {
// Not implemented.
};
/**
* Enables the Pepper Flash camera and microphone settings.
* Please note that whether the settings are actually showed or not is also
* affected by the style class pepper-flash-settings.
*/
ContentSettings.enablePepperFlashCameraMicSettings = function() {
// Not implemented.
}
// Export
return {
ContentSettings: ContentSettings
};
});
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.contentSettings', function() {
/** @const */ var ControlledSettingIndicator =
options.ControlledSettingIndicator;
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/**
* Creates a new exceptions list item.
*
* @param {string} contentType The type of the list.
* @param {string} mode The browser mode, 'otr' or 'normal'.
* @param {boolean} enableAskOption Whether to show an 'ask every time'
* option in the select.
* @param {Object} exception A dictionary that contains the data of the
* exception.
* @constructor
* @extends {options.InlineEditableItem}
*/
function ExceptionsListItem(contentType, mode, enableAskOption, exception) {
var el = cr.doc.createElement('div');
el.mode = mode;
el.contentType = contentType;
el.enableAskOption = enableAskOption;
el.dataItem = exception;
el.__proto__ = ExceptionsListItem.prototype;
el.decorate();
return el;
}
ExceptionsListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Called when an element is decorated as a list item.
*/
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
this.isPlaceholder = !this.pattern;
var patternCell = this.createEditableTextCell(this.pattern);
patternCell.className = 'exception-pattern';
patternCell.classList.add('weakrtl');
this.contentElement.appendChild(patternCell);
if (this.pattern)
this.patternLabel = patternCell.querySelector('.static-text');
var input = patternCell.querySelector('input');
// TODO(stuartmorgan): Create an createEditableSelectCell abstracting
// this code.
// Setting label for display mode. |pattern| will be null for the 'add new
// exception' row.
if (this.pattern) {
var settingLabel = cr.doc.createElement('span');
settingLabel.textContent = this.settingForDisplay();
settingLabel.className = 'exception-setting';
settingLabel.setAttribute('displaymode', 'static');
this.contentElement.appendChild(settingLabel);
this.settingLabel = settingLabel;
}
// Setting select element for edit mode.
var select = cr.doc.createElement('select');
var optionAllow = cr.doc.createElement('option');
optionAllow.textContent = loadTimeData.getString('allowException');
optionAllow.value = 'allow';
select.appendChild(optionAllow);
if (this.enableAskOption) {
var optionAsk = cr.doc.createElement('option');
optionAsk.textContent = loadTimeData.getString('askException');
optionAsk.value = 'ask';
select.appendChild(optionAsk);
}
if (this.contentType == 'cookies') {
var optionSession = cr.doc.createElement('option');
optionSession.textContent = loadTimeData.getString('sessionException');
optionSession.value = 'session';
select.appendChild(optionSession);
}
if (this.contentType != 'fullscreen') {
var optionBlock = cr.doc.createElement('option');
optionBlock.textContent = loadTimeData.getString('blockException');
optionBlock.value = 'block';
select.appendChild(optionBlock);
}
if (this.isEmbeddingRule()) {
this.patternLabel.classList.add('sublabel');
this.editable = false;
}
if (this.setting == 'default') {
// Items that don't have their own settings (parents of 'embedded on'
// items) aren't deletable.
this.deletable = false;
this.editable = false;
}
this.contentElement.appendChild(select);
select.className = 'exception-setting';
if (this.pattern)
select.setAttribute('displaymode', 'edit');
if (this.contentType == 'media-stream') {
this.settingLabel.classList.add('media-audio-setting');
var videoSettingLabel = cr.doc.createElement('span');
videoSettingLabel.textContent = this.videoSettingForDisplay();
videoSettingLabel.className = 'exception-setting';
videoSettingLabel.classList.add('media-video-setting');
videoSettingLabel.setAttribute('displaymode', 'static');
this.contentElement.appendChild(videoSettingLabel);
}
// Used to track whether the URL pattern in the input is valid.
// This will be true if the browser process has informed us that the
// current text in the input is valid. Changing the text resets this to
// false, and getting a response from the browser sets it back to true.
// It starts off as false for empty string (new exceptions) or true for
// already-existing exceptions (which we assume are valid).
this.inputValidityKnown = this.pattern;
// This one tracks the actual validity of the pattern in the input. This
// starts off as true so as not to annoy the user when he adds a new and
// empty input.
this.inputIsValid = true;
this.input = input;
this.select = select;
this.updateEditables();
// Editing notifications, geolocation and media-stream is disabled for
// now.
if (this.contentType == 'notifications' ||
this.contentType == 'location' ||
this.contentType == 'media-stream') {
this.editable = false;
}
// If the source of the content setting exception is not a user
// preference, that source controls the exception and the user cannot edit
// or delete it.
var controlledBy =
this.dataItem.source && this.dataItem.source != 'preference' ?
this.dataItem.source : null;
if (controlledBy) {
this.setAttribute('controlled-by', controlledBy);
this.deletable = false;
this.editable = false;
}
if (controlledBy == 'policy' || controlledBy == 'extension') {
this.querySelector('.row-delete-button').hidden = true;
var indicator = ControlledSettingIndicator();
indicator.setAttribute('content-exception', this.contentType);
// Create a synthetic pref change event decorated as
// CoreOptionsHandler::CreateValueForPref() does.
var event = new cr.Event(this.contentType);
event.value = { controlledBy: controlledBy };
indicator.handlePrefChange(event);
this.appendChild(indicator);
}
// If the exception comes from a hosted app, display the name and the
// icon of the app.
if (controlledBy == 'HostedApp') {
this.title =
loadTimeData.getString('set_by') + ' ' + this.dataItem.appName;
var button = this.querySelector('.row-delete-button');
// Use the host app's favicon (16px, match bigger size).
// See c/b/ui/webui/extensions/extension_icon_source.h
// for a description of the chrome://extension-icon URL.
button.style.backgroundImage =
'url(\'chrome://extension-icon/' + this.dataItem.appId + '/16/1\')';
}
var listItem = this;
// Handle events on the editable nodes.
input.oninput = function(event) {
listItem.inputValidityKnown = false;
chrome.send('checkExceptionPatternValidity',
[listItem.contentType, listItem.mode, input.value]);
};
// Listen for edit events.
this.addEventListener('canceledit', this.onEditCancelled_);
this.addEventListener('commitedit', this.onEditCommitted_);
},
isEmbeddingRule: function() {
return this.dataItem.embeddingOrigin &&
this.dataItem.embeddingOrigin !== this.dataItem.origin;
},
/**
* The pattern (e.g., a URL) for the exception.
*
* @type {string}
*/
get pattern() {
if (!this.isEmbeddingRule()) {
return this.dataItem.origin;
} else {
return loadTimeData.getStringF('embeddedOnHost',
this.dataItem.embeddingOrigin);
}
return this.dataItem.displayPattern;
},
set pattern(pattern) {
if (!this.editable)
console.error('Tried to change uneditable pattern');
this.dataItem.displayPattern = pattern;
},
/**
* The setting (allow/block) for the exception.
*
* @type {string}
*/
get setting() {
return this.dataItem.setting;
},
set setting(setting) {
this.dataItem.setting = setting;
},
/**
* Gets a human-readable setting string.
*
* @return {string} The display string.
*/
settingForDisplay: function() {
return this.getDisplayStringForSetting(this.setting);
},
/**
* media video specific function.
* Gets a human-readable video setting string.
*
* @return {string} The display string.
*/
videoSettingForDisplay: function() {
return this.getDisplayStringForSetting(this.dataItem.video);
},
/**
* Gets a human-readable display string for setting.
*
* @param {string} setting The setting to be displayed.
* @return {string} The display string.
*/
getDisplayStringForSetting: function(setting) {
if (setting == 'allow')
return loadTimeData.getString('allowException');
else if (setting == 'block')
return loadTimeData.getString('blockException');
else if (setting == 'ask')
return loadTimeData.getString('askException');
else if (setting == 'session')
return loadTimeData.getString('sessionException');
else if (setting == 'default')
return '';
console.error('Unknown setting: [' + setting + ']');
return '';
},
/**
* Update this list item to reflect whether the input is a valid pattern.
*
* @param {boolean} valid Whether said pattern is valid in the context of a
* content exception setting.
*/
setPatternValid: function(valid) {
if (valid || !this.input.value)
this.input.setCustomValidity('');
else
this.input.setCustomValidity(' ');
this.inputIsValid = valid;
this.inputValidityKnown = true;
},
/**
* Set the <input> to its original contents. Used when the user quits
* editing.
*/
resetInput: function() {
this.input.value = this.pattern;
},
/**
* Copy the data model values to the editable nodes.
*/
updateEditables: function() {
this.resetInput();
var settingOption =
this.select.querySelector('[value=\'' + this.setting + '\']');
if (settingOption)
settingOption.selected = true;
},
/** @override */
get currentInputIsValid() {
return this.inputValidityKnown && this.inputIsValid;
},
/** @override */
get hasBeenEdited() {
var livePattern = this.input.value;
var liveSetting = this.select.value;
return livePattern != this.pattern || liveSetting != this.setting;
},
/**
* Called when committing an edit.
*
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var newPattern = this.input.value;
var newSetting = this.select.value;
this.finishEdit(newPattern, newSetting);
},
/**
* Called when cancelling an edit; resets the control states.
*
* @param {Event} e The cancel event.
* @private
*/
onEditCancelled_: function() {
this.updateEditables();
this.setPatternValid(true);
},
/**
* Editing is complete; update the model.
*
* @param {string} newPattern The pattern that the user entered.
* @param {string} newSetting The setting the user chose.
*/
finishEdit: function(newPattern, newSetting) {
this.patternLabel.textContent = newPattern;
this.settingLabel.textContent = this.settingForDisplay();
var oldPattern = this.pattern;
this.pattern = newPattern;
this.setting = newSetting;
// TODO(estade): this will need to be updated if geolocation/notifications
// become editable.
if (oldPattern != newPattern) {
chrome.send('removeException',
[this.contentType, this.mode, oldPattern]);
}
chrome.send('setException',
[this.contentType, this.mode, newPattern, newSetting]);
}
};
/**
* Creates a new list item for the Add New Item row, which doesn't represent
* an actual entry in the exceptions list but allows the user to add new
* exceptions.
*
* @param {string} contentType The type of the list.
* @param {string} mode The browser mode, 'otr' or 'normal'.
* @param {boolean} enableAskOption Whether to show an 'ask every time' option
* in the select.
* @constructor
* @extends {cr.ui.ExceptionsListItem}
*/
function ExceptionsAddRowListItem(contentType, mode, enableAskOption) {
var el = cr.doc.createElement('div');
el.mode = mode;
el.contentType = contentType;
el.enableAskOption = enableAskOption;
el.dataItem = [];
el.__proto__ = ExceptionsAddRowListItem.prototype;
el.decorate();
return el;
}
ExceptionsAddRowListItem.prototype = {
__proto__: ExceptionsListItem.prototype,
decorate: function() {
ExceptionsListItem.prototype.decorate.call(this);
this.input.placeholder =
loadTimeData.getString('addNewExceptionInstructions');
// Do we always want a default of allow?
this.setting = 'allow';
},
/**
* Clear the <input> and let the placeholder text show again.
*/
resetInput: function() {
this.input.value = '';
},
/** @override */
get hasBeenEdited() {
return this.input.value != '';
},
/**
* Editing is complete; update the model. As long as the pattern isn't
* empty, we'll just add it.
*
* @param {string} newPattern The pattern that the user entered.
* @param {string} newSetting The setting the user chose.
*/
finishEdit: function(newPattern, newSetting) {
this.resetInput();
chrome.send('setException',
[this.contentType, this.mode, newPattern, newSetting]);
},
};
/**
* Creates a new exceptions list.
*
* @constructor
* @extends {cr.ui.List}
*/
var ExceptionsList = cr.ui.define('list');
ExceptionsList.prototype = {
__proto__: InlineEditableItemList.prototype,
/**
* Called when an element is decorated as a list.
*/
decorate: function() {
InlineEditableItemList.prototype.decorate.call(this);
this.classList.add('settings-list');
for (var parentNode = this.parentNode; parentNode;
parentNode = parentNode.parentNode) {
if (parentNode.hasAttribute('contentType')) {
this.contentType = parentNode.getAttribute('contentType');
break;
}
}
this.mode = this.getAttribute('mode');
// Whether the exceptions in this list allow an 'Ask every time' option.
this.enableAskOption = this.contentType == 'plugins';
this.autoExpands = true;
this.reset();
},
/**
* Creates an item to go in the list.
*
* @param {Object} entry The element from the data model for this row.
*/
createItem: function(entry) {
if (entry) {
return new ExceptionsListItem(this.contentType,
this.mode,
this.enableAskOption,
entry);
} else {
var addRowItem = new ExceptionsAddRowListItem(this.contentType,
this.mode,
this.enableAskOption);
addRowItem.deletable = false;
return addRowItem;
}
},
/**
* Sets the exceptions in the js model.
*
* @param {Object} entries A list of dictionaries of values, each dictionary
* represents an exception.
*/
setExceptions: function(entries) {
var deleteCount = this.dataModel.length;
if (this.isEditable()) {
// We don't want to remove the Add New Exception row.
deleteCount = deleteCount - 1;
}
var args = [0, deleteCount];
args.push.apply(args, entries);
this.dataModel.splice.apply(this.dataModel, args);
},
/**
* The browser has finished checking a pattern for validity. Update the list
* item to reflect this.
*
* @param {string} pattern The pattern.
* @param {bool} valid Whether said pattern is valid in the context of a
* content exception setting.
*/
patternValidityCheckComplete: function(pattern, valid) {
var listItems = this.items;
for (var i = 0; i < listItems.length; i++) {
var listItem = listItems[i];
// Don't do anything for messages for the item if it is not the intended
// recipient, or if the response is stale (i.e. the input value has
// changed since we sent the request to analyze it).
if (pattern == listItem.input.value)
listItem.setPatternValid(valid);
}
},
/**
* Returns whether the rows are editable in this list.
*/
isEditable: function() {
// Exceptions of the following lists are not editable for now.
return !(this.contentType == 'notifications' ||
this.contentType == 'location' ||
this.contentType == 'fullscreen' ||
this.contentType == 'media-stream');
},
/**
* Removes all exceptions from the js model.
*/
reset: function() {
if (this.isEditable()) {
// The null creates the Add New Exception row.
this.dataModel = new ArrayDataModel([null]);
} else {
this.dataModel = new ArrayDataModel([]);
}
},
/** @override */
deleteItemAtIndex: function(index) {
var listItem = this.getListItemByIndex(index);
if (!listItem.deletable)
return;
var dataItem = listItem.dataItem;
var args = [listItem.contentType];
if (listItem.contentType == 'notifications')
args.push(dataItem.origin, dataItem.setting);
else
args.push(listItem.mode, dataItem.origin, dataItem.embeddingOrigin);
chrome.send('removeException', args);
},
};
var OptionsPage = options.OptionsPage;
/**
* Encapsulated handling of content settings list subpage.
*
* @constructor
*/
function ContentSettingsExceptionsArea() {
OptionsPage.call(this, 'contentExceptions',
loadTimeData.getString('contentSettingsPageTabTitle'),
'content-settings-exceptions-area');
}
cr.addSingletonGetter(ContentSettingsExceptionsArea);
ContentSettingsExceptionsArea.prototype = {
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var exceptionsLists = this.pageDiv.querySelectorAll('list');
for (var i = 0; i < exceptionsLists.length; i++) {
options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]);
}
ContentSettingsExceptionsArea.hideOTRLists(false);
// If the user types in the URL without a hash, show just cookies.
this.showList('cookies');
$('content-settings-exceptions-overlay-confirm').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
},
/**
* Shows one list and hides all others.
*
* @param {string} type The content type.
*/
showList: function(type) {
var header = this.pageDiv.querySelector('h1');
header.textContent = loadTimeData.getString(type + '_header');
var divs = this.pageDiv.querySelectorAll('div[contentType]');
for (var i = 0; i < divs.length; i++) {
if (divs[i].getAttribute('contentType') == type)
divs[i].hidden = false;
else
divs[i].hidden = true;
}
var media_header = this.pageDiv.querySelector('.media-header');
media_header.hidden = type != 'media-stream';
},
/**
* Called after the page has been shown. Show the content type for the
* location's hash.
*/
didShowPage: function() {
var hash = location.hash;
if (hash)
this.showList(hash.slice(1));
},
};
/**
* Called when the last incognito window is closed.
*/
ContentSettingsExceptionsArea.OTRProfileDestroyed = function() {
this.hideOTRLists(true);
};
/**
* Hides the incognito exceptions lists and optionally clears them as well.
* @param {boolean} clear Whether to clear the lists.
*/
ContentSettingsExceptionsArea.hideOTRLists = function(clear) {
var otrLists = document.querySelectorAll('list[mode=otr]');
for (var i = 0; i < otrLists.length; i++) {
otrLists[i].parentNode.hidden = true;
if (clear)
otrLists[i].reset();
}
};
return {
ExceptionsListItem: ExceptionsListItem,
ExceptionsAddRowListItem: ExceptionsAddRowListItem,
ExceptionsList: ExceptionsList,
ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
//////////////////////////////////////////////////////////////////////////////
// ContentSettingsRadio class:
// Define a constructor that uses an input element as its underlying element.
var ContentSettingsRadio = cr.ui.define('input');
ContentSettingsRadio.prototype = {
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'radio';
var self = this;
this.addEventListener('change',
function(e) {
chrome.send('setContentFilter', [this.name, this.value]);
});
},
};
/**
* Whether the content setting is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(ContentSettingsRadio, 'controlledBy', cr.PropertyKind.ATTR);
//////////////////////////////////////////////////////////////////////////////
// HandlersEnabledRadio class:
// Define a constructor that uses an input element as its underlying element.
var HandlersEnabledRadio = cr.ui.define('input');
HandlersEnabledRadio.prototype = {
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'radio';
var self = this;
this.addEventListener('change',
function(e) {
chrome.send('setHandlersEnabled', [this.value == 'allow']);
});
},
};
// Export
return {
ContentSettingsRadio: ContentSettingsRadio,
HandlersEnabledRadio: HandlersEnabledRadio
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var DeletableItemList = options.DeletableItemList;
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
// This structure maps the various cookie type names from C++ (hence the
// underscores) to arrays of the different types of data each has, along with
// the i18n name for the description of that data type.
/** @const */ var cookieInfo = {
'cookie': [['name', 'label_cookie_name'],
['content', 'label_cookie_content'],
['domain', 'label_cookie_domain'],
['path', 'label_cookie_path'],
['sendfor', 'label_cookie_send_for'],
['accessibleToScript', 'label_cookie_accessible_to_script'],
['created', 'label_cookie_created'],
['expires', 'label_cookie_expires']],
'app_cache': [['manifest', 'label_app_cache_manifest'],
['size', 'label_local_storage_size'],
['created', 'label_cookie_created'],
['accessed', 'label_cookie_last_accessed']],
'database': [['name', 'label_cookie_name'],
['desc', 'label_webdb_desc'],
['size', 'label_local_storage_size'],
['modified', 'label_local_storage_last_modified']],
'local_storage': [['origin', 'label_local_storage_origin'],
['size', 'label_local_storage_size'],
['modified', 'label_local_storage_last_modified']],
'indexed_db': [['origin', 'label_indexed_db_origin'],
['size', 'label_indexed_db_size'],
['modified', 'label_indexed_db_last_modified']],
'file_system': [['origin', 'label_file_system_origin'],
['persistent', 'label_file_system_persistent_usage'],
['temporary', 'label_file_system_temporary_usage']],
'server_bound_cert': [['serverId', 'label_server_bound_cert_server_id'],
['certType', 'label_server_bound_cert_type'],
['created', 'label_server_bound_cert_created'],
['expires', 'label_server_bound_cert_expires']],
'flash_lso': [['domain', 'label_cookie_domain']],
};
/**
* Returns the item's height, like offsetHeight but such that it works better
* when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
* This version also accounts for the animation done in this file.
* @param {Element} item The item to get the height of.
* @return {number} The height of the item, calculated with zooming in mind.
*/
function getItemHeight(item) {
var height = item.style.height;
// Use the fixed animation target height if set, in case the element is
// currently being animated and we'd get an intermediate height below.
if (height && height.substr(-2) == 'px')
return parseInt(height.substr(0, height.length - 2));
return item.getBoundingClientRect().height;
}
/**
* Create tree nodes for the objects in the data array, and insert them all
* into the given list using its @{code splice} method at the given index.
* @param {Array.<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
* @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added.
*/
function spliceTreeNodes(data, start, list) {
var nodes = data.map(function(x) { return new CookieTreeNode(x); });
// Insert [start, 0] at the beginning of the array of nodes, making it
// into the arguments we want to pass to @{code list.splice} below.
nodes.splice(0, 0, start, 0);
list.splice.apply(list, nodes);
// Remove the [start, 0] prefix and return the array of nodes.
nodes.splice(0, 2);
return nodes;
}
/**
* Adds information about an app that protects this data item to the
* @{code element}.
* @param {Element} element The DOM element the information should be
appended to.
* @param {{id: string, name: string}} appInfo Information about an app.
*/
function addAppInfo(element, appInfo) {
var img = element.ownerDocument.createElement('img');
img.src = 'chrome://extension-icon/' + appInfo.id + '/16/1';
element.title = loadTimeData.getString('label_protected_by_apps') +
' ' + appInfo.name;
img.className = 'protecting-app';
element.appendChild(img);
}
var parentLookup = {};
var lookupRequests = {};
/**
* Creates a new list item for sites data. Note that these are created and
* destroyed lazily as they scroll into and out of view, so they must be
* stateless. We cache the expanded item in @{code CookiesList} though, so it
* can keep state. (Mostly just which item is selected.)
* @param {Object} origin Data used to create a cookie list item.
* @param {CookiesList} list The list that will contain this item.
* @constructor
* @extends {DeletableItem}
*/
function CookieListItem(origin, list) {
var listItem = new DeletableItem(null);
listItem.__proto__ = CookieListItem.prototype;
listItem.origin = origin;
listItem.list = list;
listItem.decorate();
// This hooks up updateOrigin() to the list item, makes the top-level
// tree nodes (i.e., origins) register their IDs in parentLookup, and
// causes them to request their children if they have none. Note that we
// have special logic in the setter for the parent property to make sure
// that we can still garbage collect list items when they scroll out of
// view, even though it appears that we keep a direct reference.
if (origin) {
origin.parent = listItem;
origin.updateOrigin();
}
return listItem;
}
CookieListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @override */
decorate: function() {
this.siteChild = this.ownerDocument.createElement('div');
this.siteChild.className = 'cookie-site';
this.dataChild = this.ownerDocument.createElement('div');
this.dataChild.className = 'cookie-data';
this.sizeChild = this.ownerDocument.createElement('div');
this.sizeChild.className = 'cookie-size';
this.itemsChild = this.ownerDocument.createElement('div');
this.itemsChild.className = 'cookie-items';
this.infoChild = this.ownerDocument.createElement('div');
this.infoChild.className = 'cookie-details';
this.infoChild.hidden = true;
var remove = this.ownerDocument.createElement('button');
remove.textContent = loadTimeData.getString('remove_cookie');
remove.onclick = this.removeCookie_.bind(this);
this.infoChild.appendChild(remove);
var content = this.contentElement;
content.appendChild(this.siteChild);
content.appendChild(this.dataChild);
content.appendChild(this.sizeChild);
content.appendChild(this.itemsChild);
this.itemsChild.appendChild(this.infoChild);
if (this.origin && this.origin.data) {
this.siteChild.textContent = this.origin.data.title;
this.siteChild.setAttribute('title', this.origin.data.title);
}
this.itemList_ = [];
},
/** @type {boolean} */
get expanded() {
return this.expanded_;
},
set expanded(expanded) {
if (this.expanded_ == expanded)
return;
this.expanded_ = expanded;
if (expanded) {
var oldExpanded = this.list.expandedItem;
this.list.expandedItem = this;
this.updateItems_();
if (oldExpanded)
oldExpanded.expanded = false;
this.classList.add('show-items');
} else {
if (this.list.expandedItem == this) {
this.list.expandedItem = null;
}
this.style.height = '';
this.itemsChild.style.height = '';
this.classList.remove('show-items');
}
},
/**
* The callback for the "remove" button shown when an item is selected.
* Requests that the currently selected cookie be removed.
* @private
*/
removeCookie_: function() {
if (this.selectedIndex_ >= 0) {
var item = this.itemList_[this.selectedIndex_];
if (item && item.node)
chrome.send('removeCookie', [item.node.pathId]);
}
},
/**
* Disable animation within this cookie list item, in preparation for making
* changes that will need to be animated. Makes it possible to measure the
* contents without displaying them, to set animation targets.
* @private
*/
disableAnimation_: function() {
this.itemsHeight_ = getItemHeight(this.itemsChild);
this.classList.add('measure-items');
},
/**
* Enable animation after changing the contents of this cookie list item.
* See @{code disableAnimation_}.
* @private
*/
enableAnimation_: function() {
if (!this.classList.contains('measure-items'))
this.disableAnimation_();
this.itemsChild.style.height = '';
// This will force relayout in order to calculate the new heights.
var itemsHeight = getItemHeight(this.itemsChild);
var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_;
this.itemsChild.style.height = this.itemsHeight_ + 'px';
// Force relayout before enabling animation, so that if we have
// changed things since the last layout, they will not be animated
// during subsequent layouts.
this.itemsChild.offsetHeight;
this.classList.remove('measure-items');
this.itemsChild.style.height = itemsHeight + 'px';
this.style.height = fixedHeight + 'px';
},
/**
* Updates the origin summary to reflect changes in its items.
* Both CookieListItem and CookieTreeNode implement this API.
* This implementation scans the descendants to update the text.
*/
updateOrigin: function() {
var info = {
cookies: 0,
database: false,
localStorage: false,
appCache: false,
indexedDb: false,
fileSystem: false,
serverBoundCerts: 0,
};
if (this.origin)
this.origin.collectSummaryInfo(info);
var list = [];
if (info.cookies > 1)
list.push(loadTimeData.getStringF('cookie_plural', info.cookies));
else if (info.cookies > 0)
list.push(loadTimeData.getString('cookie_singular'));
if (info.database || info.indexedDb)
list.push(loadTimeData.getString('cookie_database_storage'));
if (info.localStorage)
list.push(loadTimeData.getString('cookie_local_storage'));
if (info.appCache)
list.push(loadTimeData.getString('cookie_app_cache'));
if (info.fileSystem)
list.push(loadTimeData.getString('cookie_file_system'));
if (info.serverBoundCerts)
list.push(loadTimeData.getString('cookie_server_bound_cert'));
if (info.flashLSO)
list.push(loadTimeData.getString('cookie_flash_lso'));
var text = '';
for (var i = 0; i < list.length; ++i) {
if (text.length > 0)
text += ', ' + list[i];
else
text = list[i];
}
this.dataChild.textContent = text;
for (var key in info.appsProtectingThis) {
addAppInfo(this.dataChild, apps[key]);
}
if (info.quota && info.quota.totalUsage)
this.sizeChild.textContent = info.quota.totalUsage;
if (this.expanded)
this.updateItems_();
},
/**
* Updates the items section to reflect changes, animating to the new state.
* Removes existing contents and calls @{code CookieTreeNode.createItems}.
* @private
*/
updateItems_: function() {
this.disableAnimation_();
this.itemsChild.textContent = '';
this.infoChild.hidden = true;
this.selectedIndex_ = -1;
this.itemList_ = [];
if (this.origin)
this.origin.createItems(this);
this.itemsChild.appendChild(this.infoChild);
this.enableAnimation_();
},
/**
* Append a new cookie node "bubble" to this list item.
* @param {CookieTreeNode} node The cookie node to add a bubble for.
* @param {Element} div The DOM element for the bubble itself.
* @return {number} The index the bubble was added at.
*/
appendItem: function(node, div) {
this.itemList_.push({node: node, div: div});
this.itemsChild.appendChild(div);
return this.itemList_.length - 1;
},
/**
* The currently selected cookie node ("cookie bubble") index.
* @type {number}
* @private
*/
selectedIndex_: -1,
/**
* Get the currently selected cookie node ("cookie bubble") index.
* @type {number}
*/
get selectedIndex() {
return this.selectedIndex_;
},
/**
* Set the currently selected cookie node ("cookie bubble") index to
* @{code itemIndex}, unselecting any previously selected node first.
* @param {number} itemIndex The index to set as the selected index.
*/
set selectedIndex(itemIndex) {
// Get the list index up front before we change anything.
var index = this.list.getIndexOfListItem(this);
// Unselect any previously selected item.
if (this.selectedIndex_ >= 0) {
var item = this.itemList_[this.selectedIndex_];
if (item && item.div)
item.div.removeAttribute('selected');
}
// Special case: decrementing -1 wraps around to the end of the list.
if (itemIndex == -2)
itemIndex = this.itemList_.length - 1;
// Check if we're going out of bounds and hide the item details.
if (itemIndex < 0 || itemIndex >= this.itemList_.length) {
this.selectedIndex_ = -1;
this.disableAnimation_();
this.infoChild.hidden = true;
this.enableAnimation_();
return;
}
// Set the new selected item and show the item details for it.
this.selectedIndex_ = itemIndex;
this.itemList_[itemIndex].div.setAttribute('selected', '');
this.disableAnimation_();
this.itemList_[itemIndex].node.setDetailText(this.infoChild,
this.list.infoNodes);
this.infoChild.hidden = false;
this.enableAnimation_();
// If we're near the bottom of the list this may cause the list item to go
// beyond the end of the visible area. Fix it after the animation is done.
var list = this.list;
window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150);
},
};
/**
* {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and
* contain all the actual data used to generate the {@code CookieListItem}s.
* @param {Object} data The data object for this node.
* @constructor
*/
function CookieTreeNode(data) {
this.data = data;
this.children = [];
}
CookieTreeNode.prototype = {
/**
* Insert the given list of cookie tree nodes at the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {Array.<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
*/
insertAt: function(data, start) {
var nodes = spliceTreeNodes(data, start, this.children);
for (var i = 0; i < nodes.length; i++)
nodes[i].parent = this;
this.updateOrigin();
},
/**
* Remove a cookie tree node from the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {number} index The index of the tree node to remove.
*/
remove: function(index) {
if (index < this.children.length) {
this.children.splice(index, 1);
this.updateOrigin();
}
},
/**
* Clears all children.
* Both CookiesList and CookieTreeNode implement this API.
* It is used by CookiesList.loadChildren().
*/
clear: function() {
// We might leave some garbage in parentLookup for removed children.
// But that should be OK because parentLookup is cleared when we
// reload the tree.
this.children = [];
this.updateOrigin();
},
/**
* The counter used by startBatchUpdates() and endBatchUpdates().
* @type {number}
*/
batchCount_: 0,
/**
* See cr.ui.List.startBatchUpdates().
* Both CookiesList (via List) and CookieTreeNode implement this API.
*/
startBatchUpdates: function() {
this.batchCount_++;
},
/**
* See cr.ui.List.endBatchUpdates().
* Both CookiesList (via List) and CookieTreeNode implement this API.
*/
endBatchUpdates: function() {
if (!--this.batchCount_)
this.updateOrigin();
},
/**
* Requests updating the origin summary to reflect changes in this item.
* Both CookieListItem and CookieTreeNode implement this API.
*/
updateOrigin: function() {
if (!this.batchCount_ && this.parent)
this.parent.updateOrigin();
},
/**
* Summarize the information in this node and update @{code info}.
* This will recurse into child nodes to summarize all descendants.
* @param {Object} info The info object from @{code updateOrigin}.
*/
collectSummaryInfo: function(info) {
if (this.children.length > 0) {
for (var i = 0; i < this.children.length; ++i)
this.children[i].collectSummaryInfo(info);
} else if (this.data && !this.data.hasChildren) {
if (this.data.type == 'cookie') {
info.cookies++;
} else if (this.data.type == 'database') {
info.database = true;
} else if (this.data.type == 'local_storage') {
info.localStorage = true;
} else if (this.data.type == 'app_cache') {
info.appCache = true;
} else if (this.data.type == 'indexed_db') {
info.indexedDb = true;
} else if (this.data.type == 'file_system') {
info.fileSystem = true;
} else if (this.data.type == 'quota') {
info.quota = this.data;
} else if (this.data.type == 'server_bound_cert') {
info.serverBoundCerts++;
} else if (this.data.type == 'flash_lso') {
info.flashLSO = true;
}
var apps = this.data.appsProtectingThis;
if (apps) {
if (!info.appsProtectingThis)
info.appsProtectingThis = {};
apps.forEach(function(appInfo) {
info.appsProtectingThis[appInfo.id] = appInfo;
});
}
}
},
/**
* Create the cookie "bubbles" for this node, recursing into children
* if there are any. Append the cookie bubbles to @{code item}.
* @param {CookieListItem} item The cookie list item to create items in.
*/
createItems: function(item) {
if (this.children.length > 0) {
for (var i = 0; i < this.children.length; ++i)
this.children[i].createItems(item);
return;
}
if (!this.data || this.data.hasChildren)
return;
var text = '';
switch (this.data.type) {
case 'cookie':
case 'database':
text = this.data.name;
break;
default:
text = loadTimeData.getString('cookie_' + this.data.type);
}
if (!text)
return;
var div = item.ownerDocument.createElement('div');
div.className = 'cookie-item';
// Help out screen readers and such: this is a clickable thing.
div.setAttribute('role', 'button');
div.tabIndex = 0;
div.textContent = text;
var apps = this.data.appsProtectingThis;
if (apps)
apps.forEach(addAppInfo.bind(null, div));
var index = item.appendItem(this, div);
div.onclick = function() {
item.selectedIndex = (item.selectedIndex == index) ? -1 : index;
};
},
/**
* Set the detail text to be displayed to that of this cookie tree node.
* Uses preallocated DOM elements for each cookie node type from @{code
* infoNodes}, and inserts the appropriate elements to @{code element}.
* @param {Element} element The DOM element to insert elements to.
* @param {Object.<string, {table: Element, info: Object.<string,
* Element>}>} infoNodes The map from cookie node types to maps from
* cookie attribute names to DOM elements to display cookie attribute
* values, created by @{code CookiesList.decorate}.
*/
setDetailText: function(element, infoNodes) {
var table;
if (this.data && !this.data.hasChildren && cookieInfo[this.data.type]) {
var info = cookieInfo[this.data.type];
var nodes = infoNodes[this.data.type].info;
for (var i = 0; i < info.length; ++i) {
var name = info[i][0];
if (name != 'id' && this.data[name])
nodes[name].textContent = this.data[name];
else
nodes[name].textContent = '';
}
table = infoNodes[this.data.type].table;
}
while (element.childNodes.length > 1)
element.removeChild(element.firstChild);
if (table)
element.insertBefore(table, element.firstChild);
},
/**
* The parent of this cookie tree node.
* @type {?CookieTreeNode|CookieListItem}
*/
get parent() {
// See below for an explanation of this special case.
if (typeof this.parent_ == 'number')
return this.list_.getListItemByIndex(this.parent_);
return this.parent_;
},
set parent(parent) {
if (parent == this.parent)
return;
if (parent instanceof CookieListItem) {
// If the parent is to be a CookieListItem, then we keep the reference
// to it by its containing list and list index, rather than directly.
// This allows the list items to be garbage collected when they scroll
// out of view (except the expanded item, which we cache). This is
// transparent except in the setter and getter, where we handle it.
if (this.parent_ == undefined || parent.listIndex != -1) {
// Setting the parent is somewhat tricky because the CookieListItem
// constructor has side-effects on the |origin| that it wraps. Every
// time a CookieListItem is created for an |origin|, it registers
// itself as the parent of the |origin|.
// The List implementation may create a temporary CookieListItem item
// that wraps the |origin| of the very first entry of the CokiesList,
// when the List is redrawn the first time. This temporary
// CookieListItem is fresh (has listIndex = -1) and is never inserted
// into the List. Therefore it gets never updated. This destroys the
// chain of parent pointers.
// This is the stack trace:
// CookieListItem
// CookiesList.createItem
// List.measureItem
// List.getDefaultItemSize_
// List.getDefaultItemHeight_
// List.getIndexForListOffset_
// List.getItemsInViewPort
// List.redraw
// List.endBatchUpdates
// CookiesList.loadChildren
this.parent_ = parent.listIndex;
}
this.list_ = parent.list;
parent.addEventListener('listIndexChange',
this.parentIndexChanged_.bind(this));
} else {
this.parent_ = parent;
}
if (this.data && this.data.id) {
if (parent)
parentLookup[this.data.id] = this;
else
delete parentLookup[this.data.id];
}
if (this.data && this.data.hasChildren &&
!this.children.length && !lookupRequests[this.data.id]) {
lookupRequests[this.data.id] = true;
chrome.send('loadCookie', [this.pathId]);
}
},
/**
* Called when the parent is a CookieListItem whose index has changed.
* See the code above that avoids keeping a direct reference to
* CookieListItem parents, to allow them to be garbage collected.
* @private
*/
parentIndexChanged_: function(event) {
if (typeof this.parent_ == 'number') {
this.parent_ = event.newValue;
// We set a timeout to update the origin, rather than doing it right
// away, because this callback may occur while the list items are
// being repopulated following a scroll event. Calling updateOrigin()
// immediately could trigger relayout that would reset the scroll
// position within the list, among other things.
window.setTimeout(this.updateOrigin.bind(this), 0);
}
},
/**
* The cookie tree path id.
* @type {string}
*/
get pathId() {
var parent = this.parent;
if (parent && parent instanceof CookieTreeNode)
return parent.pathId + ',' + this.data.id;
return this.data.id;
},
};
/**
* Creates a new cookies list.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {DeletableItemList}
*/
var CookiesList = cr.ui.define('list');
CookiesList.prototype = {
__proto__: DeletableItemList.prototype,
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.classList.add('cookie-list');
this.dataModel = new ArrayDataModel([]);
this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this));
var sm = new ListSingleSelectionModel();
sm.addEventListener('change', this.cookieSelectionChange_.bind(this));
sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this));
this.selectionModel = sm;
this.infoNodes = {};
this.fixedHeight = false;
var doc = this.ownerDocument;
// Create a table for each type of site data (e.g. cookies, databases,
// etc.) and save it so that we can reuse it for all origins.
for (var type in cookieInfo) {
var table = doc.createElement('table');
table.className = 'cookie-details-table';
var tbody = doc.createElement('tbody');
table.appendChild(tbody);
var info = {};
for (var i = 0; i < cookieInfo[type].length; i++) {
var tr = doc.createElement('tr');
var name = doc.createElement('td');
var data = doc.createElement('td');
var pair = cookieInfo[type][i];
name.className = 'cookie-details-label';
name.textContent = loadTimeData.getString(pair[1]);
data.className = 'cookie-details-value';
data.textContent = '';
tr.appendChild(name);
tr.appendChild(data);
tbody.appendChild(tr);
info[pair[0]] = data;
}
this.infoNodes[type] = {table: table, info: info};
}
},
/**
* Handles key down events and looks for left and right arrows, then
* dispatches to the currently expanded item, if any.
* @param {Event} e The keydown event.
* @private
*/
handleKeyLeftRight_: function(e) {
var id = e.keyIdentifier;
if ((id == 'Left' || id == 'Right') && this.expandedItem) {
var cs = this.ownerDocument.defaultView.getComputedStyle(this);
var rtl = cs.direction == 'rtl';
if ((!rtl && id == 'Left') || (rtl && id == 'Right'))
this.expandedItem.selectedIndex--;
else
this.expandedItem.selectedIndex++;
this.scrollIndexIntoView(this.expandedItem.listIndex);
// Prevent the page itself from scrolling.
e.preventDefault();
}
},
/**
* Called on selection model selection changes.
* @param {Event} ce The selection change event.
* @private
*/
cookieSelectionChange_: function(ce) {
ce.changes.forEach(function(change) {
var listItem = this.getListItemByIndex(change.index);
if (listItem) {
if (!change.selected) {
// We set a timeout here, rather than setting the item unexpanded
// immediately, so that if another item gets set expanded right
// away, it will be expanded before this item is unexpanded. It
// will notice that, and unexpand this item in sync with its own
// expansion. Later, this callback will end up having no effect.
window.setTimeout(function() {
if (!listItem.selected || !listItem.lead)
listItem.expanded = false;
}, 0);
} else if (listItem.lead) {
listItem.expanded = true;
}
}
}, this);
},
/**
* Called on selection model lead changes.
* @param {Event} pe The lead change event.
* @private
*/
cookieLeadChange_: function(pe) {
if (pe.oldValue != -1) {
var listItem = this.getListItemByIndex(pe.oldValue);
if (listItem) {
// See cookieSelectionChange_ above for why we use a timeout here.
window.setTimeout(function() {
if (!listItem.lead || !listItem.selected)
listItem.expanded = false;
}, 0);
}
}
if (pe.newValue != -1) {
var listItem = this.getListItemByIndex(pe.newValue);
if (listItem && listItem.selected)
listItem.expanded = true;
}
},
/**
* The currently expanded item. Used by CookieListItem above.
* @type {?CookieListItem}
*/
expandedItem: null,
// from cr.ui.List
/** @override */
createItem: function(data) {
// We use the cached expanded item in order to allow it to maintain some
// state (like its fixed height, and which bubble is selected).
if (this.expandedItem && this.expandedItem.origin == data)
return this.expandedItem;
return new CookieListItem(data, this);
},
// from options.DeletableItemList
/** @override */
deleteItemAtIndex: function(index) {
var item = this.dataModel.item(index);
if (item) {
var pathId = item.pathId;
if (pathId)
chrome.send('removeCookie', [pathId]);
}
},
/**
* Insert the given list of cookie tree nodes at the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {Array.<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
*/
insertAt: function(data, start) {
spliceTreeNodes(data, start, this.dataModel);
},
/**
* Remove a cookie tree node from the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {number} index The index of the tree node to remove.
*/
remove: function(index) {
if (index < this.dataModel.length)
this.dataModel.splice(index, 1);
},
/**
* Clears the list.
* Both CookiesList and CookieTreeNode implement this API.
* It is used by CookiesList.loadChildren().
*/
clear: function() {
parentLookup = {};
this.dataModel.splice(0, this.dataModel.length);
this.redraw();
},
/**
* Add tree nodes by given parent.
* @param {Object} parent The parent node.
* @param {number} start The index at which to start inserting the nodes.
* @param {Array} nodesData Nodes data array.
* @private
*/
addByParent_: function(parent, start, nodesData) {
if (!parent)
return;
parent.startBatchUpdates();
parent.insertAt(nodesData, start);
parent.endBatchUpdates();
cr.dispatchSimpleEvent(this, 'change');
},
/**
* Add tree nodes by parent id.
* This is used by cookies_view.js.
* @param {string} parentId Id of the parent node.
* @param {number} start The index at which to start inserting the nodes.
* @param {Array} nodesData Nodes data array.
*/
addByParentId: function(parentId, start, nodesData) {
var parent = parentId ? parentLookup[parentId] : this;
this.addByParent_(parent, start, nodesData);
},
/**
* Removes tree nodes by parent id.
* This is used by cookies_view.js.
* @param {string} parentId Id of the parent node.
* @param {number} start The index at which to start removing the nodes.
* @param {number} count Number of nodes to remove.
*/
removeByParentId: function(parentId, start, count) {
var parent = parentId ? parentLookup[parentId] : this;
if (!parent)
return;
parent.startBatchUpdates();
while (count-- > 0)
parent.remove(start);
parent.endBatchUpdates();
cr.dispatchSimpleEvent(this, 'change');
},
/**
* Loads the immediate children of given parent node.
* This is used by cookies_view.js.
* @param {string} parentId Id of the parent node.
* @param {Array} children The immediate children of parent node.
*/
loadChildren: function(parentId, children) {
if (parentId)
delete lookupRequests[parentId];
var parent = parentId ? parentLookup[parentId] : this;
if (!parent)
return;
parent.startBatchUpdates();
parent.clear();
this.addByParent_(parent, 0, children);
parent.endBatchUpdates();
},
};
return {
CookiesList: CookiesList
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/////////////////////////////////////////////////////////////////////////////
// CookiesView class:
/**
* Encapsulated handling of the cookies and other site data page.
* @constructor
*/
function CookiesView(model) {
OptionsPage.call(this, 'cookies',
loadTimeData.getString('cookiesViewPageTabTitle'),
'cookies-view-page');
}
cr.addSingletonGetter(CookiesView);
CookiesView.prototype = {
__proto__: OptionsPage.prototype,
/**
* The timer id of the timer set on search query change events.
* @type {number}
* @private
*/
queryDelayTimerId_: 0,
/**
* The most recent search query, empty string if the query is empty.
* @type {string}
* @private
*/
lastQuery_: '',
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.pageDiv.querySelector('.cookies-search-box').addEventListener(
'search', this.handleSearchQueryChange_.bind(this));
this.pageDiv.querySelector('.remove-all-cookies-button').onclick =
function(e) {
chrome.send('removeAllCookies');
};
var cookiesList = this.pageDiv.querySelector('.cookies-list');
options.CookiesList.decorate(cookiesList);
this.addEventListener('visibleChange', this.handleVisibleChange_);
this.pageDiv.querySelector('.cookies-view-overlay-confirm').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
},
/** @override */
didShowPage: function() {
this.pageDiv.querySelector('.cookies-search-box').value = '';
},
/**
* Search cookie using text in |cookies-search-box|.
*/
searchCookie: function() {
this.queryDelayTimerId_ = 0;
var filter = this.pageDiv.querySelector('.cookies-search-box').value;
if (this.lastQuery_ != filter) {
this.lastQuery_ = filter;
chrome.send('updateCookieSearchResults', [filter]);
}
},
/**
* Handles search query changes.
* @param {!Event} e The event object.
* @private
*/
handleSearchQueryChange_: function(e) {
if (this.queryDelayTimerId_)
window.clearTimeout(this.queryDelayTimerId_);
this.queryDelayTimerId_ = window.setTimeout(
this.searchCookie.bind(this), 500);
},
initialized_: false,
/**
* Handler for OptionsPage's visible property change event.
* @param {Event} e Property change event.
* @private
*/
handleVisibleChange_: function(e) {
if (!this.visible)
return;
chrome.send('reloadCookies');
if (!this.initialized_) {
this.initialized_ = true;
this.searchCookie();
} else {
this.pageDiv.querySelector('.cookies-list').redraw();
}
this.pageDiv.querySelector('.cookies-search-box').focus();
},
};
// CookiesViewHandler callbacks.
CookiesView.onTreeItemAdded = function(args) {
$('cookies-list').addByParentId(args[0], args[1], args[2]);
};
CookiesView.onTreeItemRemoved = function(args) {
$('cookies-list').removeByParentId(args[0], args[1], args[2]);
};
CookiesView.loadChildren = function(args) {
$('cookies-list').loadChildren(args[0], args[1]);
};
// Export
return {
CookiesView: CookiesView
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* FactoryResetOverlay class
* Encapsulated handling of the Factory Reset confirmation overlay page.
* @class
*/
function FactoryResetOverlay() {
OptionsPage.call(this, 'factoryResetData',
loadTimeData.getString('factoryResetTitle'),
'factory-reset-overlay');
}
cr.addSingletonGetter(FactoryResetOverlay);
FactoryResetOverlay.prototype = {
// Inherit FactoryResetOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
$('factory-reset-data-dismiss').onclick = function(event) {
FactoryResetOverlay.dismiss();
};
$('factory-reset-data-restart').onclick = function(event) {
chrome.send('performFactoryResetRestart');
};
},
};
FactoryResetOverlay.dismiss = function() {
OptionsPage.closeOverlay();
};
// Export
return {
FactoryResetOverlay: FactoryResetOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
if (loadTimeData.getBoolean('managedUsersEnabled')) {
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
//////////////////////////////////////////////////////////////////////////////
// ManagedUserSettings class:
/**
* Encapsulated handling of the Managed User Settings page.
* @constructor
* @class
*/
function ManagedUserSettings() {
OptionsPage.call(
this,
'manageduser',
loadTimeData.getString('managedUserSettingsPageTabTitle'),
'managed-user-settings-page');
}
cr.addSingletonGetter(ManagedUserSettings);
ManagedUserSettings.prototype = {
// Inherit from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
* @override
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
$('get-content-packs-button').onclick = function(event) {
window.open(loadTimeData.getString('getContentPacksURL'));
};
$('managed-user-settings-confirm').onclick = function() {
OptionsPage.closeOverlay();
};
$('set-passphrase').onclick = function() {
// TODO(bauerb): Set passphrase
};
},
};
// Export
return {
ManagedUserSettings: ManagedUserSettings
};
});
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* FontSettings class
* Encapsulated handling of the 'Fonts and Encoding' page.
* @class
*/
function FontSettings() {
OptionsPage.call(this,
'fonts',
loadTimeData.getString('fontSettingsPageTabTitle'),
'font-settings');
}
cr.addSingletonGetter(FontSettings);
FontSettings.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var standardFontRange = $('standard-font-size');
standardFontRange.valueMap = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20,
22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72];
standardFontRange.addEventListener(
'change', this.standardRangeChanged_.bind(this, standardFontRange));
standardFontRange.customChangeHandler =
this.standardFontSizeChanged_.bind(standardFontRange);
var minimumFontRange = $('minimum-font-size');
minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 20, 22, 24];
minimumFontRange.addEventListener(
'change', this.minimumRangeChanged_.bind(this, minimumFontRange));
minimumFontRange.customChangeHandler =
this.minimumFontSizeChanged_.bind(minimumFontRange);
var placeholder = loadTimeData.getString('fontSettingsPlaceholder');
var elements = [$('standard-font-family'), $('serif-font-family'),
$('sans-serif-font-family'), $('fixed-font-family'),
$('font-encoding')];
elements.forEach(function(el) {
el.appendChild(new Option(placeholder));
el.setDisabled('noFontsAvailable', true);
});
$('font-settings-confirm').onclick = function() {
OptionsPage.closeOverlay();
};
},
/**
* Called by the options page when this page has been shown.
*/
didShowPage: function() {
// The fonts list may be large so we only load it when this page is
// loaded for the first time. This makes opening the options window
// faster and consume less memory if the user never opens the fonts
// dialog.
if (!this.hasShown) {
chrome.send('fetchFontsData');
this.hasShown = true;
}
},
/**
* Handler that is called when the user changes the position of the standard
* font size slider. This allows the UI to show a preview of the change
* before the slider has been released and the associated prefs updated.
* @param {Element} el The slider input element.
* @param {Event} event Change event.
* @private
*/
standardRangeChanged_: function(el, event) {
var size = el.mapPositionToPref(el.value);
var fontSampleEl = $('standard-font-sample');
this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
true);
fontSampleEl = $('serif-font-sample');
this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
true);
fontSampleEl = $('sans-serif-font-sample');
this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
true);
fontSampleEl = $('fixed-font-sample');
this.setUpFontSample_(fontSampleEl,
size - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD,
fontSampleEl.style.fontFamily, false);
},
/**
* Sets the 'default_fixed_font_size' preference when the user changes the
* standard font size.
* @param {Event} event Change event.
* @private
*/
standardFontSizeChanged_: function(event) {
var size = this.mapPositionToPref(this.value);
Preferences.setIntegerPref(
'webkit.webprefs.default_fixed_font_size',
size - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
return false;
},
/**
* Handler that is called when the user changes the position of the minimum
* font size slider. This allows the UI to show a preview of the change
* before the slider has been released and the associated prefs updated.
* @param {Element} el The slider input element.
* @param {Event} event Change event.
* @private
*/
minimumRangeChanged_: function(el, event) {
var size = el.mapPositionToPref(el.value);
var fontSampleEl = $('minimum-font-sample');
this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
true);
},
/**
* Sets the 'minimum_logical_font_size' preference when the user changes the
* minimum font size.
* @param {Event} event Change event.
* @private
*/
minimumFontSizeChanged_: function(event) {
var size = this.mapPositionToPref(this.value);
Preferences.setIntegerPref(
'webkit.webprefs.minimum_logical_font_size', size, true);
return false;
},
/**
* Sets the text, font size and font family of the sample text.
* @param {Element} el The div containing the sample text.
* @param {number} size The font size of the sample text.
* @param {string} font The font family of the sample text.
* @param {bool} showSize True if the font size should appear in the sample.
* @private
*/
setUpFontSample_: function(el, size, font, showSize) {
var prefix = showSize ? (size + ': ') : '';
el.textContent = prefix +
loadTimeData.getString('fontSettingsLoremIpsum');
el.style.fontSize = size + 'px';
if (font)
el.style.fontFamily = font;
},
/**
* Populates a select list and selects the specified item.
* @param {Element} element The select element to populate.
* @param {Array} items The array of items from which to populate.
* @param {string} selectedValue The selected item.
* @private
*/
populateSelect_: function(element, items, selectedValue) {
// Remove any existing content.
element.textContent = '';
// Insert new child nodes into select element.
var value, text, selected, option;
for (var i = 0; i < items.length; i++) {
value = items[i][0];
text = items[i][1];
dir = items[i][2];
if (text) {
selected = value == selectedValue;
var option = new Option(text, value, false, selected);
option.dir = dir;
element.appendChild(option);
} else {
element.appendChild(document.createElement('hr'));
}
}
element.setDisabled('noFontsAvailable', false);
}
};
// Chrome callbacks
FontSettings.setFontsData = function(fonts, encodings, selectedValues) {
FontSettings.getInstance().populateSelect_($('standard-font-family'), fonts,
selectedValues[0]);
FontSettings.getInstance().populateSelect_($('serif-font-family'), fonts,
selectedValues[1]);
FontSettings.getInstance().populateSelect_($('sans-serif-font-family'),
fonts, selectedValues[2]);
FontSettings.getInstance().populateSelect_($('fixed-font-family'), fonts,
selectedValues[3]);
FontSettings.getInstance().populateSelect_($('font-encoding'), encodings,
selectedValues[4]);
};
FontSettings.setUpStandardFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('standard-font-sample'), size,
font, true);
};
FontSettings.setUpSerifFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('serif-font-sample'), size,
font, true);
};
FontSettings.setUpSansSerifFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('sans-serif-font-sample'),
size, font, true);
};
FontSettings.setUpFixedFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('fixed-font-sample'),
size, font, false);
};
FontSettings.setUpMinimumFontSample = function(size) {
// If size is less than 6, represent it as six in the sample to account
// for the minimum logical font size.
if (size < 6)
size = 6;
FontSettings.getInstance().setUpFontSample_($('minimum-font-sample'), size,
null, true);
};
// Export
return {
FontSettings: FontSettings
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/////////////////////////////////////////////////////////////////////////////
// HandlerOptions class:
/**
* Encapsulated handling of handler options page.
* @constructor
*/
function HandlerOptions() {
this.activeNavTab = null;
OptionsPage.call(this,
'handlers',
loadTimeData.getString('handlersPageTabTitle'),
'handler-options');
}
cr.addSingletonGetter(HandlerOptions);
HandlerOptions.prototype = {
__proto__: OptionsPage.prototype,
/**
* The handlers list.
* @type {DeletableItemList}
* @private
*/
handlersList_: null,
/** @override */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.createHandlersList_();
$('handler-options-overlay-confirm').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
},
/**
* Creates, decorates and initializes the handlers list.
* @private
*/
createHandlersList_: function() {
this.handlersList_ = $('handlers-list');
options.HandlersList.decorate(this.handlersList_);
this.handlersList_.autoExpands = true;
this.ignoredHandlersList_ = $('ignored-handlers-list');
options.IgnoredHandlersList.decorate(this.ignoredHandlersList_);
this.ignoredHandlersList_.autoExpands = true;
},
};
/**
* Sets the list of handlers shown by the view.
* @param {Array} Handlers to be shown in the view.
*/
HandlerOptions.setHandlers = function(handlers) {
$('handlers-list').setHandlers(handlers);
};
/**
* Sets the list of ignored handlers shown by the view.
* @param {Array} Handlers to be shown in the view.
*/
HandlerOptions.setIgnoredHandlers = function(handlers) {
$('ignored-handlers-section').hidden = handlers.length == 0;
$('ignored-handlers-list').setHandlers(handlers);
};
return {
HandlerOptions: HandlerOptions
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var List = cr.ui.List;
/** @const */ var ListItem = cr.ui.ListItem;
/** @const */ var HandlerOptions = options.HandlerOptions;
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/**
* Creates a new ignored protocol / content handler list item.
*
* Accepts values in the form
* ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
* @param {Object} entry A dictionary describing the handlers for a given
* protocol.
* @constructor
* @extends {cr.ui.DeletableItemList}
*/
function IgnoredHandlersListItem(entry) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = IgnoredHandlersListItem.prototype;
el.decorate();
return el;
}
IgnoredHandlersListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// Protocol.
var protocolElement = document.createElement('div');
protocolElement.textContent = this.dataItem[0];
protocolElement.className = 'handlers-type-column';
this.contentElement_.appendChild(protocolElement);
// Site title.
var titleElement = document.createElement('div');
titleElement.textContent = this.dataItem[2];
titleElement.className = 'handlers-site-column';
titleElement.title = this.dataItem[1];
this.contentElement_.appendChild(titleElement);
},
};
var IgnoredHandlersList = cr.ui.define('list');
IgnoredHandlersList.prototype = {
__proto__: DeletableItemList.prototype,
createItem: function(entry) {
return new IgnoredHandlersListItem(entry);
},
deleteItemAtIndex: function(index) {
chrome.send('removeIgnoredHandler', [this.dataModel.item(index)]);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
/**
* Set the protocol handlers displayed by this list. See
* IgnoredHandlersListItem for an example of the format the list should
* take.
*
* @param {Object} list A list of ignored protocol handlers.
*/
setHandlers: function(list) {
this.dataModel = new ArrayDataModel(list);
},
};
/**
* Creates a new protocol / content handler list item.
*
* Accepts values in the form
* { protocol: 'mailto',
* handlers: [
* ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
* ...,
* ],
* }
* @param {Object} entry A dictionary describing the handlers for a given
* protocol.
* @constructor
* @extends {cr.ui.ListItem}
*/
function HandlerListItem(entry) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = HandlerListItem.prototype;
el.decorate();
return el;
}
HandlerListItem.prototype = {
__proto__: ListItem.prototype,
buildWidget_: function(data, delegate) {
// Protocol.
var protocolElement = document.createElement('div');
protocolElement.textContent = data.protocol;
protocolElement.className = 'handlers-type-column';
this.appendChild(protocolElement);
// Handler selection.
var handlerElement = document.createElement('div');
var selectElement = document.createElement('select');
var defaultOptionElement = document.createElement('option');
defaultOptionElement.selected = data.default_handler == -1;
defaultOptionElement.textContent =
loadTimeData.getString('handlers_none_handler');
defaultOptionElement.value = -1;
selectElement.appendChild(defaultOptionElement);
for (var i = 0; i < data.handlers.length; ++i) {
var optionElement = document.createElement('option');
optionElement.selected = i == data.default_handler;
optionElement.textContent = data.handlers[i][2];
optionElement.value = i;
selectElement.appendChild(optionElement);
}
selectElement.addEventListener('change', function(e) {
var index = e.target.value;
if (index == -1) {
this.classList.add('none');
delegate.clearDefault(data.protocol);
} else {
handlerElement.classList.remove('none');
delegate.setDefault(data.handlers[index]);
}
});
handlerElement.appendChild(selectElement);
handlerElement.className = 'handlers-site-column';
if (data.default_handler == -1)
this.classList.add('none');
this.appendChild(handlerElement);
// Remove link.
var removeElement = document.createElement('div');
removeElement.textContent =
loadTimeData.getString('handlers_remove_link');
removeElement.addEventListener('click', function(e) {
var value = selectElement ? selectElement.value : 0;
delegate.removeHandler(value, data.handlers[value]);
});
removeElement.className = 'handlers-remove-column handlers-remove-link';
this.appendChild(removeElement);
},
/** @override */
decorate: function() {
ListItem.prototype.decorate.call(this);
var self = this;
var delegate = {
removeHandler: function(index, handler) {
chrome.send('removeHandler', [handler]);
},
setDefault: function(handler) {
chrome.send('setDefault', [handler]);
},
clearDefault: function(protocol) {
chrome.send('clearDefault', [protocol]);
},
};
this.buildWidget_(this.dataItem, delegate);
},
};
/**
* Create a new passwords list.
* @constructor
* @extends {cr.ui.List}
*/
var HandlersList = cr.ui.define('list');
HandlersList.prototype = {
__proto__: List.prototype,
/** @override */
createItem: function(entry) {
return new HandlerListItem(entry);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
/**
* Set the protocol handlers displayed by this list.
* See HandlerListItem for an example of the format the list should take.
*
* @param {Object} list A list of protocols with their registered handlers.
*/
setHandlers: function(list) {
this.dataModel = new ArrayDataModel(list);
},
};
return {
IgnoredHandlersListItem: IgnoredHandlersListItem,
IgnoredHandlersList: IgnoredHandlersList,
HandlerListItem: HandlerListItem,
HandlersList: HandlersList,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/** @const */ var SettingsDialog = options.SettingsDialog;
/**
* HomePageOverlay class
* Dialog that allows users to set the home page.
* @extends {SettingsDialog}
*/
function HomePageOverlay() {
SettingsDialog.call(this, 'homePageOverlay',
loadTimeData.getString('homePageOverlayTabTitle'),
'home-page-overlay',
$('home-page-confirm'), $('home-page-cancel'));
}
cr.addSingletonGetter(HomePageOverlay);
HomePageOverlay.prototype = {
__proto__: SettingsDialog.prototype,
/**
* An autocomplete list that can be attached to the home page URL field.
* @type {cr.ui.AutocompleteList}
* @private
*/
autocompleteList_: null,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
SettingsDialog.prototype.initializePage.call(this);
var self = this;
options.Preferences.getInstance().addEventListener(
'homepage_is_newtabpage',
this.handleHomepageIsNTPPrefChange.bind(this));
var urlField = $('homepage-url-field');
urlField.addEventListener('keydown', function(event) {
// Focus the 'OK' button when the user hits enter since people expect
// feedback indicating that they are done editing.
if (event.keyIdentifier == 'Enter' && self.autocompleteList_.hidden)
$('home-page-confirm').focus();
});
urlField.addEventListener('change', this.updateFavicon_.bind(this));
var suggestionList = new cr.ui.AutocompleteList();
suggestionList.autoExpands = true;
suggestionList.suggestionUpdateRequestCallback =
this.requestAutocompleteSuggestions_.bind(this);
$('home-page-overlay').appendChild(suggestionList);
this.autocompleteList_ = suggestionList;
urlField.addEventListener('focus', function(event) {
self.autocompleteList_.attachToInput(urlField);
});
urlField.addEventListener('blur', function(event) {
self.autocompleteList_.detach();
});
// Text fields may change widths when the window changes size, so make
// sure the suggestion list stays in sync.
window.addEventListener('resize', function() {
self.autocompleteList_.syncWidthToInput();
});
},
/** @override */
didShowPage: function() {
this.updateFavicon_();
},
/**
* Updates the state of the homepage text input and its controlled setting
* indicator when the |homepage_is_newtabpage| pref changes. The input is
* enabled only if the homepage is not the NTP. The indicator is always
* enabled but treats the input's value as read-only if the homepage is the
* NTP.
* @param {Event} Pref change event.
*/
handleHomepageIsNTPPrefChange: function(event) {
var urlField = $('homepage-url-field');
var urlFieldIndicator = $('homepage-url-field-indicator');
urlField.setDisabled('homepage-is-ntp', event.value.value);
urlFieldIndicator.readOnly = event.value.value;
},
/**
* Updates the background of the url field to show the favicon for the
* URL that is currently typed in.
* @private
*/
updateFavicon_: function() {
var urlField = $('homepage-url-field');
urlField.style.backgroundImage = url(getFaviconURL(urlField.value));
},
/**
* Sends an asynchronous request for new autocompletion suggestions for the
* the given query. When new suggestions are available, the C++ handler will
* call updateAutocompleteSuggestions_.
* @param {string} query List of autocomplete suggestions.
* @private
*/
requestAutocompleteSuggestions_: function(query) {
chrome.send('requestAutocompleteSuggestionsForHomePage', [query]);
},
/**
* Updates the autocomplete suggestion list with the given entries.
* @param {Array} pages List of autocomplete suggestions.
* @private
*/
updateAutocompleteSuggestions_: function(suggestions) {
var list = this.autocompleteList_;
// If the trigger for this update was a value being selected from the
// current list, do nothing.
if (list.targetInput && list.selectedItem &&
list.selectedItem.url == list.targetInput.value) {
return;
}
list.suggestions = suggestions;
},
/**
* Sets the 'show home button' and 'home page is new tab page' preferences.
* (The home page url preference is set automatically by the SettingsDialog
* code.)
*/
handleConfirm: function() {
// Strip whitespace.
var urlField = $('homepage-url-field');
var homePageValue = urlField.value.replace(/\s*/g, '');
urlField.value = homePageValue;
// Don't save an empty URL for the home page. If the user left the field
// empty, switch to the New Tab page.
if (!homePageValue)
$('homepage-use-ntp').checked = true;
SettingsDialog.prototype.handleConfirm.call(this);
},
};
HomePageOverlay.updateAutocompleteSuggestions = function() {
var instance = HomePageOverlay.getInstance();
instance.updateAutocompleteSuggestions_.apply(instance, arguments);
};
// Export
return {
HomePageOverlay: HomePageOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* ImportDataOverlay class
* Encapsulated handling of the 'Import Data' overlay page.
* @class
*/
function ImportDataOverlay() {
OptionsPage.call(this,
'importData',
loadTimeData.getString('importDataOverlayTabTitle'),
'import-data-overlay');
}
cr.addSingletonGetter(ImportDataOverlay);
ImportDataOverlay.prototype = {
// Inherit from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
var checkboxes =
document.querySelectorAll('#import-checkboxes input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].onchange = function() {
self.validateCommitButton_();
};
}
$('import-browsers').onchange = function() {
self.updateCheckboxes_();
self.validateCommitButton_();
};
$('import-data-commit').onclick = function() {
chrome.send('importData', [
String($('import-browsers').selectedIndex),
String($('import-history').checked),
String($('import-favorites').checked),
String($('import-passwords').checked),
String($('import-search').checked)]);
};
$('import-data-cancel').onclick = function() {
ImportDataOverlay.dismiss();
};
$('import-data-show-bookmarks-bar').onchange = function() {
// Note: The callback 'toggleShowBookmarksBar' is handled within the
// browser options handler -- rather than the import data handler --
// as the implementation is shared by several clients.
chrome.send('toggleShowBookmarksBar');
}
$('import-data-confirm').onclick = function() {
ImportDataOverlay.dismiss();
};
// Form controls are disabled until the profile list has been loaded.
self.setControlsSensitive_(false);
},
/**
* Set enabled and checked state of the commit button.
* @private
*/
validateCommitButton_: function() {
var somethingToImport =
$('import-history').checked || $('import-favorites').checked ||
$('import-passwords').checked || $('import-search').checked;
$('import-data-commit').disabled = !somethingToImport;
},
/**
* Sets the sensitivity of all the checkboxes and the commit button.
* @private
*/
setControlsSensitive_: function(sensitive) {
var checkboxes =
document.querySelectorAll('#import-checkboxes input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++)
this.setUpCheckboxState_(checkboxes[i], sensitive);
$('import-data-commit').disabled = !sensitive;
},
/**
* Set enabled and checked states a checkbox element.
* @param {Object} checkbox A checkbox element.
* @param {boolean} enabled The enabled state of the chekbox.
* @private
*/
setUpCheckboxState_: function(checkbox, enabled) {
checkbox.setDisabled('noProfileData', !enabled);
},
/**
* Update the enabled and checked states of all checkboxes.
* @private
*/
updateCheckboxes_: function() {
var index = $('import-browsers').selectedIndex;
var browserProfile;
if (this.browserProfiles.length > index)
browserProfile = this.browserProfiles[index];
var importOptions = ['history', 'favorites', 'passwords', 'search'];
for (var i = 0; i < importOptions.length; i++) {
var checkbox = $('import-' + importOptions[i]);
var enable = browserProfile && browserProfile[importOptions[i]];
this.setUpCheckboxState_(checkbox, enable);
}
},
/**
* Update the supported browsers popup with given entries.
* @param {array} browsers List of supported browsers name.
* @private
*/
updateSupportedBrowsers_: function(browsers) {
this.browserProfiles = browsers;
var browserSelect = $('import-browsers');
browserSelect.remove(0); // Remove the 'Loading...' option.
browserSelect.textContent = '';
var browserCount = browsers.length;
if (browserCount == 0) {
var option = new Option(loadTimeData.getString('noProfileFound'), 0);
browserSelect.appendChild(option);
this.setControlsSensitive_(false);
} else {
this.setControlsSensitive_(true);
for (var i = 0; i < browserCount; i++) {
var browser = browsers[i];
var option = new Option(browser.name, browser.index);
browserSelect.appendChild(option);
}
this.updateCheckboxes_();
this.validateCommitButton_();
}
},
/**
* Clear import prefs set when user checks/unchecks a checkbox so that each
* checkbox goes back to the default "checked" state (or alternatively, to
* the state set by a recommended policy).
* @private
*/
clearUserPrefs_: function() {
var importPrefs = ['import_history',
'import_bookmarks',
'import_saved_passwords',
'import_search_engine'];
for (var i = 0; i < importPrefs.length; i++)
Preferences.clearPref(importPrefs[i], true);
},
/**
* Update the dialog layout to reflect success state.
* @param {boolean} success If true, show success dialog elements.
* @private
*/
updateSuccessState_: function(success) {
var sections = document.querySelectorAll('.import-data-configure');
for (var i = 0; i < sections.length; i++)
sections[i].hidden = success;
sections = document.querySelectorAll('.import-data-success');
for (var i = 0; i < sections.length; i++)
sections[i].hidden = !success;
},
};
ImportDataOverlay.clearUserPrefs = function() {
ImportDataOverlay.getInstance().clearUserPrefs_();
};
/**
* Update the supported browsers popup with given entries.
* @param {array} list of supported browsers name.
*/
ImportDataOverlay.updateSupportedBrowsers = function(browsers) {
ImportDataOverlay.getInstance().updateSupportedBrowsers_(browsers);
};
/**
* Update the UI to reflect whether an import operation is in progress.
* @param {boolean} state True if an import operation is in progress.
*/
ImportDataOverlay.setImportingState = function(state) {
var checkboxes =
document.querySelectorAll('#import-checkboxes input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++)
checkboxes[i].setDisabled('Importing', state);
if (!state)
ImportDataOverlay.getInstance().updateCheckboxes_();
$('import-browsers').disabled = state;
$('import-throbber').style.visibility = state ? 'visible' : 'hidden';
ImportDataOverlay.getInstance().validateCommitButton_();
};
/**
* Remove the import overlay from display.
*/
ImportDataOverlay.dismiss = function() {
ImportDataOverlay.clearUserPrefs();
OptionsPage.closeOverlay();
};
/**
* Show a message confirming the success of the import operation.
*/
ImportDataOverlay.confirmSuccess = function() {
var showBookmarksMessage = $('import-favorites').checked;
ImportDataOverlay.setImportingState(false);
$('import-find-your-bookmarks').hidden = !showBookmarksMessage;
ImportDataOverlay.getInstance().updateSuccessState_(true);
};
/**
* Show the import data overlay.
*/
ImportDataOverlay.show = function() {
// Make sure that any previous import success message is hidden, and
// we're showing the UI to import further data.
ImportDataOverlay.getInstance().updateSuccessState_(false);
OptionsPage.navigateToPage('importData');
};
// Export
return {
ImportDataOverlay: ImportDataOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
///////////////////////////////////////////////////////////////////////////////
// AddLanguageOverlay class:
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/**
* Encapsulated handling of ChromeOS add language overlay page.
* @constructor
*/
function AddLanguageOverlay() {
OptionsPage.call(this, 'addLanguage',
loadTimeData.getString('add_button'),
'add-language-overlay-page');
}
cr.addSingletonGetter(AddLanguageOverlay);
AddLanguageOverlay.prototype = {
// Inherit AddLanguageOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initializes AddLanguageOverlay page.
* Calls base class implementation to starts preference initialization.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
// Set up the cancel button.
$('add-language-overlay-cancel-button').onclick = function(e) {
OptionsPage.closeOverlay();
};
// Create the language list with which users can add a language.
var addLanguageList = $('add-language-overlay-language-list');
var languageListData = loadTimeData.getValue('languageList');
for (var i = 0; i < languageListData.length; i++) {
var language = languageListData[i];
var displayText = language.displayName;
// If the native name is different, add it.
if (language.displayName != language.nativeDisplayName) {
displayText += ' - ' + language.nativeDisplayName;
}
if (cr.isChromeOS) {
var button = document.createElement('button');
button.className = 'link-button';
button.textContent = displayText;
button.languageCode = language.code;
var li = document.createElement('li');
li.languageCode = language.code;
li.appendChild(button);
addLanguageList.appendChild(li);
} else {
var option = document.createElement('option');
option.value = language.code;
option.textContent = displayText;
addLanguageList.appendChild(option);
}
}
},
};
return {
AddLanguageOverlay: AddLanguageOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.dictionary_words', function() {
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/**
* Creates a new dictionary word list item.
* @param {string} dictionaryWord The dictionary word.
* @param {function(string)} addDictionaryWordCallback Callback for
* adding a dictionary word.
* @constructor
* @extends {cr.ui.InlineEditableItem}
*/
function DictionaryWordsListItem(dictionaryWord, addDictionaryWordCallback) {
var el = cr.doc.createElement('div');
el.dictionaryWord_ = dictionaryWord;
el.addDictionaryWordCallback_ = addDictionaryWordCallback;
DictionaryWordsListItem.decorate(el);
return el;
}
/**
* Decorates an HTML element as a dictionary word list item.
* @param {HTMLElement} el The element to decorate.
*/
DictionaryWordsListItem.decorate = function(el) {
el.__proto__ = DictionaryWordsListItem.prototype;
el.decorate();
};
DictionaryWordsListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
if (this.dictionaryWord_ == '')
this.isPlaceholder = true;
else
this.editable = false;
var wordEl = this.createEditableTextCell(this.dictionaryWord_);
wordEl.classList.add('weakrtl');
wordEl.classList.add('language-dictionary-overlay-word-list-item');
this.contentElement.appendChild(wordEl);
var wordField = wordEl.querySelector('input');
wordField.classList.add('language-dictionary-overlay-word-list-item');
if (this.isPlaceholder) {
wordField.placeholder =
loadTimeData.getString('languageDictionaryOverlayAddWordLabel');
}
this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
},
/** @override */
get hasBeenEdited() {
return this.querySelector('input').value.length > 0;
},
/**
* Adds a word to the dictionary.
* @param {Event} e Edit committed event.
* @private
*/
onEditCommitted_: function(e) {
var input = e.currentTarget.querySelector('input');
this.addDictionaryWordCallback_(input.value);
input.value = '';
},
};
/**
* A list of words in the dictionary.
* @extends {cr.ui.InlineEditableItemList}
*/
var DictionaryWordsList = cr.ui.define('list');
DictionaryWordsList.prototype = {
__proto__: InlineEditableItemList.prototype,
/**
* The function to notify that the word list has changed.
* @type {function()}
*/
onWordListChanged: null,
/**
* The list of all words in the dictionary. Used to generate a filtered word
* list in the |search(searchTerm)| method.
* @type {Array}
* @private
*/
allWordsList_: null,
/**
* Add a dictionary word.
* @param {string} dictionaryWord The word to add.
* @private
*/
addDictionaryWord_: function(dictionaryWord) {
this.allWordsList_.push(dictionaryWord);
this.dataModel.splice(this.dataModel.length - 1, 0, dictionaryWord);
this.onWordListChanged();
chrome.send('addDictionaryWord', [dictionaryWord]);
},
/**
* Search the list for the matching words.
* @param {string} searchTerm The search term.
*/
search: function(searchTerm) {
var filteredWordList = this.allWordsList_.filter(
function(element, index, array) {
return element.indexOf(searchTerm) > -1;
});
filteredWordList.push('');
this.dataModel = new cr.ui.ArrayDataModel(filteredWordList);
this.onWordListChanged();
},
/**
* Set the list of dictionary words.
* @param {Array} entries The list of dictionary words.
*/
setWordList: function(entries) {
this.allWordsList_ = entries.slice(0);
// Empty string is a placeholder for entering new words.
entries.push('');
this.dataModel = new cr.ui.ArrayDataModel(entries);
this.onWordListChanged();
},
/**
* True if the data model contains no words, otherwise false.
* @type {boolean}
*/
get empty() {
// A data model without dictionary words contains one placeholder for
// entering new words.
return this.dataModel.length < 2;
},
/** @override */
createItem: function(dictionaryWord) {
return new DictionaryWordsListItem(
dictionaryWord,
this.addDictionaryWord_.bind(this));
},
/** @override */
deleteItemAtIndex: function(index) {
// The last element in the data model is an undeletable placeholder for
// entering new words.
assert(index > -1 && index < this.dataModel.length - 1);
var item = this.dataModel.item(index);
var allWordsListIndex = this.allWordsList_.indexOf(item);
assert(allWordsListIndex > -1);
this.allWordsList_.splice(allWordsListIndex, 1);
this.dataModel.splice(index, 1);
this.onWordListChanged();
chrome.send('removeDictionaryWord', [item]);
},
/** @override */
shouldFocusPlaceholder: function() {
return false;
},
};
return {
DictionaryWordsList: DictionaryWordsList
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var DictionaryWordsList =
options.dictionary_words.DictionaryWordsList;
/** @const */ var OptionsPage = options.OptionsPage;
/**
* Adding and removing words in custom spelling dictionary.
* @constructor
* @extends {options.OptionsPage}
*/
function EditDictionaryOverlay() {
OptionsPage.call(this, 'editDictionary',
loadTimeData.getString('languageDictionaryOverlayPage'),
'language-dictionary-overlay-page');
}
cr.addSingletonGetter(EditDictionaryOverlay);
EditDictionaryOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* A list of words in the dictionary.
* @type {DictionaryWordsList}
* @private
*/
wordList_: null,
/**
* The input field for searching for words in the dictionary.
* @type {HTMLElement}
* @private
*/
searchField_: null,
/**
* The paragraph of text that indicates that search returned no results.
* @type {HTMLElement}
* @private
*/
noMatchesLabel_: null,
/**
* Initializes the edit dictionary overlay.
* @override
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.wordList_ = $('language-dictionary-overlay-word-list');
DictionaryWordsList.decorate(this.wordList_);
this.wordList_.onWordListChanged = function() {
this.onWordListChanged_();
}.bind(this);
this.searchField_ = $('language-dictionary-overlay-search-field');
this.searchField_.onsearch = function(e) {
this.wordList_.search(e.currentTarget.value);
}.bind(this);
this.noMatchesLabel_ = getRequiredElement(
'language-dictionary-overlay-no-matches');
$('language-dictionary-overlay-done-button').onclick = function(e) {
OptionsPage.closeOverlay();
};
},
/**
* Refresh the dictionary words when the page is displayed.
* @override
*/
didShowPage: function() {
chrome.send('refreshDictionaryWords');
},
/**
* Update the view based on the changes in the word list.
* @private
*/
onWordListChanged_: function() {
if (this.searchField_.value.length > 0 && this.wordList_.empty) {
this.noMatchesLabel_.hidden = false;
this.wordList_.classList.add('no-search-matches');
} else {
this.noMatchesLabel_.hidden = true;
this.wordList_.classList.remove('no-search-matches');
}
},
};
EditDictionaryOverlay.setWordList = function(entries) {
EditDictionaryOverlay.getInstance().wordList_.setWordList(entries);
};
EditDictionaryOverlay.getWordListForTesting = function() {
return EditDictionaryOverlay.getInstance().wordList_;
};
return {
EditDictionaryOverlay: EditDictionaryOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/** @const */ var List = cr.ui.List;
/** @const */ var ListItem = cr.ui.ListItem;
/** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
/**
* Creates a new Language list item.
* @param {Object} languageInfo The information of the language.
* @constructor
* @extends {DeletableItem.ListItem}
*/
function LanguageListItem(languageInfo) {
var el = cr.doc.createElement('li');
el.__proto__ = LanguageListItem.prototype;
el.language_ = languageInfo;
el.decorate();
return el;
};
LanguageListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* The language code of this language.
* @type {String}
* @private
*/
languageCode_: null,
/** @override */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
var languageCode = this.language_.code;
var languageOptions = options.LanguageOptions.getInstance();
this.deletable = languageOptions.languageIsDeletable(languageCode);
this.languageCode = languageCode;
this.languageName = cr.doc.createElement('div');
this.languageName.className = 'language-name';
this.languageName.dir = this.language_.textDirection;
this.languageName.textContent = this.language_.displayName;
this.contentElement.appendChild(this.languageName);
this.title = this.language_.nativeDisplayName;
this.draggable = true;
},
};
/**
* Creates a new language list.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {cr.ui.List}
*/
var LanguageList = cr.ui.define('list');
/**
* Gets information of a language from the given language code.
* @param {string} languageCode Language code (ex. "fr").
*/
LanguageList.getLanguageInfoFromLanguageCode = function(languageCode) {
// Build the language code to language info dictionary at first time.
if (!this.languageCodeToLanguageInfo_) {
this.languageCodeToLanguageInfo_ = {};
var languageList = loadTimeData.getValue('languageList');
for (var i = 0; i < languageList.length; i++) {
var languageInfo = languageList[i];
this.languageCodeToLanguageInfo_[languageInfo.code] = languageInfo;
}
}
return this.languageCodeToLanguageInfo_[languageCode];
}
/**
* Returns true if the given language code is valid.
* @param {string} languageCode Language code (ex. "fr").
*/
LanguageList.isValidLanguageCode = function(languageCode) {
// Having the display name for the language code means that the
// language code is valid.
if (LanguageList.getLanguageInfoFromLanguageCode(languageCode)) {
return true;
}
return false;
}
LanguageList.prototype = {
__proto__: DeletableItemList.prototype,
// The list item being dragged.
draggedItem: null,
// The drop position information: "below" or "above".
dropPos: null,
// The preference is a CSV string that describes preferred languages
// in Chrome OS. The language list is used for showing the language
// list in "Language and Input" options page.
preferredLanguagesPref: 'settings.language.preferred_languages',
// The preference is a CSV string that describes accept languages used
// for content negotiation. To be more precise, the list will be used
// in "Accept-Language" header in HTTP requests.
acceptLanguagesPref: 'intl.accept_languages',
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.selectionModel = new ListSingleSelectionModel;
// HACK(arv): http://crbug.com/40902
window.addEventListener('resize', this.redraw.bind(this));
// Listen to pref change.
if (cr.isChromeOS) {
Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
this.handlePreferredLanguagesPrefChange_.bind(this));
} else {
Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
this.handleAcceptLanguagesPrefChange_.bind(this));
}
// Listen to drag and drop events.
this.addEventListener('dragstart', this.handleDragStart_.bind(this));
this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
this.addEventListener('dragover', this.handleDragOver_.bind(this));
this.addEventListener('drop', this.handleDrop_.bind(this));
this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
},
createItem: function(languageCode) {
languageInfo = LanguageList.getLanguageInfoFromLanguageCode(languageCode);
return new LanguageListItem(languageInfo);
},
/*
* For each item, determines whether it's deletable.
*/
updateDeletable: function() {
var items = this.items;
for (var i = 0; i < items.length; ++i) {
var item = items[i];
var languageCode = item.languageCode;
var languageOptions = options.LanguageOptions.getInstance();
item.deletable = languageOptions.languageIsDeletable(languageCode);
}
},
/*
* Adds a language to the language list.
* @param {string} languageCode language code (ex. "fr").
*/
addLanguage: function(languageCode) {
// It shouldn't happen but ignore the language code if it's
// null/undefined, or already present.
if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
return;
}
this.dataModel.push(languageCode);
// Select the last item, which is the language added.
this.selectionModel.selectedIndex = this.dataModel.length - 1;
this.savePreference_();
},
/*
* Gets the language codes of the currently listed languages.
*/
getLanguageCodes: function() {
return this.dataModel.slice();
},
/*
* Clears the selection
*/
clearSelection: function() {
this.selectionModel.unselectAll();
},
/*
* Gets the language code of the selected language.
*/
getSelectedLanguageCode: function() {
return this.selectedItem;
},
/*
* Selects the language by the given language code.
* @returns {boolean} True if the operation is successful.
*/
selectLanguageByCode: function(languageCode) {
var index = this.dataModel.indexOf(languageCode);
if (index >= 0) {
this.selectionModel.selectedIndex = index;
return true;
}
return false;
},
/** @override */
deleteItemAtIndex: function(index) {
if (index >= 0) {
this.dataModel.splice(index, 1);
// Once the selected item is removed, there will be no selected item.
// Select the item pointed by the lead index.
index = this.selectionModel.leadIndex;
this.savePreference_();
}
return index;
},
/*
* Computes the target item of drop event.
* @param {Event} e The drop or dragover event.
* @private
*/
getTargetFromDropEvent_: function(e) {
var target = e.target;
// e.target may be an inner element of the list item
while (target != null && !(target instanceof ListItem)) {
target = target.parentNode;
}
return target;
},
/*
* Handles the dragstart event.
* @param {Event} e The dragstart event.
* @private
*/
handleDragStart_: function(e) {
var target = e.target;
// ListItem should be the only draggable element type in the page,
// but just in case.
if (target instanceof ListItem) {
this.draggedItem = target;
e.dataTransfer.effectAllowed = 'move';
// We need to put some kind of data in the drag or it will be
// ignored. Use the display name in case the user drags to a text
// field or the desktop.
e.dataTransfer.setData('text/plain', target.title);
}
},
/*
* Handles the dragenter event.
* @param {Event} e The dragenter event.
* @private
*/
handleDragEnter_: function(e) {
e.preventDefault();
},
/*
* Handles the dragover event.
* @param {Event} e The dragover event.
* @private
*/
handleDragOver_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
// Determines whether the drop target is to accept the drop.
// The drop is only successful on another ListItem.
if (!(dropTarget instanceof ListItem) ||
dropTarget == this.draggedItem) {
this.hideDropMarker_();
return;
}
// Compute the drop postion. Should we move the dragged item to
// below or above the drop target?
var rect = dropTarget.getBoundingClientRect();
var dy = e.clientY - rect.top;
var yRatio = dy / rect.height;
var dropPos = yRatio <= .5 ? 'above' : 'below';
this.dropPos = dropPos;
this.showDropMarker_(dropTarget, dropPos);
e.preventDefault();
},
/*
* Handles the drop event.
* @param {Event} e The drop event.
* @private
*/
handleDrop_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
this.hideDropMarker_();
// Delete the language from the original position.
var languageCode = this.draggedItem.languageCode;
var originalIndex = this.dataModel.indexOf(languageCode);
this.dataModel.splice(originalIndex, 1);
// Insert the language to the new position.
var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
if (this.dropPos == 'below')
newIndex += 1;
this.dataModel.splice(newIndex, 0, languageCode);
// The cursor should move to the moved item.
this.selectionModel.selectedIndex = newIndex;
// Save the preference.
this.savePreference_();
},
/*
* Handles the dragleave event.
* @param {Event} e The dragleave event
* @private
*/
handleDragLeave_: function(e) {
this.hideDropMarker_();
},
/*
* Shows and positions the marker to indicate the drop target.
* @param {HTMLElement} target The current target list item of drop
* @param {string} pos 'below' or 'above'
* @private
*/
showDropMarker_: function(target, pos) {
window.clearTimeout(this.hideDropMarkerTimer_);
var marker = $('language-options-list-dropmarker');
var rect = target.getBoundingClientRect();
var markerHeight = 8;
if (pos == 'above') {
marker.style.top = (rect.top - markerHeight / 2) + 'px';
} else {
marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
}
marker.style.width = rect.width + 'px';
marker.style.left = rect.left + 'px';
marker.style.display = 'block';
},
/*
* Hides the drop marker.
* @private
*/
hideDropMarker_: function() {
// Hide the marker in a timeout to reduce flickering as we move between
// valid drop targets.
window.clearTimeout(this.hideDropMarkerTimer_);
this.hideDropMarkerTimer_ = window.setTimeout(function() {
$('language-options-list-dropmarker').style.display = '';
}, 100);
},
/**
* Handles preferred languages pref change.
* @param {Event} e The change event object.
* @private
*/
handlePreferredLanguagesPrefChange_: function(e) {
var languageCodesInCsv = e.value.value;
var languageCodes = languageCodesInCsv.split(',');
// Add the UI language to the initial list of languages. This is to avoid
// a bug where the UI language would be removed from the preferred
// language list by sync on first login.
// See: crosbug.com/14283
languageCodes.push(navigator.language);
languageCodes = this.filterBadLanguageCodes_(languageCodes);
this.load_(languageCodes);
},
/**
* Handles accept languages pref change.
* @param {Event} e The change event object.
* @private
*/
handleAcceptLanguagesPrefChange_: function(e) {
var languageCodesInCsv = e.value.value;
var languageCodes = this.filterBadLanguageCodes_(
languageCodesInCsv.split(','));
this.load_(languageCodes);
},
/**
* Loads given language list.
* @param {Array} languageCodes List of language codes.
* @private
*/
load_: function(languageCodes) {
// Preserve the original selected index. See comments below.
var originalSelectedIndex = (this.selectionModel ?
this.selectionModel.selectedIndex : -1);
this.dataModel = new ArrayDataModel(languageCodes);
if (originalSelectedIndex >= 0 &&
originalSelectedIndex < this.dataModel.length) {
// Restore the original selected index if the selected index is
// valid after the data model is loaded. This is neeeded to keep
// the selected language after the languge is added or removed.
this.selectionModel.selectedIndex = originalSelectedIndex;
// The lead index should be updated too.
this.selectionModel.leadIndex = originalSelectedIndex;
} else if (this.dataModel.length > 0) {
// Otherwise, select the first item if it's not empty.
// Note that ListSingleSelectionModel won't select an item
// automatically, hence we manually select the first item here.
this.selectionModel.selectedIndex = 0;
}
},
/**
* Saves the preference.
*/
savePreference_: function() {
// Encode the language codes into a CSV string.
if (cr.isChromeOS)
Preferences.setStringPref(this.preferredLanguagesPref,
this.dataModel.slice().join(','), true);
// Save the same language list as accept languages preference as
// well, but we need to expand the language list, to make it more
// acceptable. For instance, some web sites don't understand 'en-US'
// but 'en'. See crosbug.com/9884.
var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
Preferences.setStringPref(this.acceptLanguagesPref,
acceptLanguages.join(','), true);
cr.dispatchSimpleEvent(this, 'save');
},
/**
* Expands language codes to make these more suitable for Accept-Language.
* Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
* 'en' won't appear twice as this function eliminates duplicates.
* @param {Array} languageCodes List of language codes.
* @private
*/
expandLanguageCodes: function(languageCodes) {
var expandedLanguageCodes = [];
var seen = {}; // Used to eliminiate duplicates.
for (var i = 0; i < languageCodes.length; i++) {
var languageCode = languageCodes[i];
if (!(languageCode in seen)) {
expandedLanguageCodes.push(languageCode);
seen[languageCode] = true;
}
var parts = languageCode.split('-');
if (!(parts[0] in seen)) {
expandedLanguageCodes.push(parts[0]);
seen[parts[0]] = true;
}
}
return expandedLanguageCodes;
},
/**
* Filters bad language codes in case bad language codes are
* stored in the preference. Removes duplicates as well.
* @param {Array} languageCodes List of language codes.
* @private
*/
filterBadLanguageCodes_: function(languageCodes) {
var filteredLanguageCodes = [];
var seen = {};
for (var i = 0; i < languageCodes.length; i++) {
// Check if the the language code is valid, and not
// duplicate. Otherwise, skip it.
if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
!(languageCodes[i] in seen)) {
filteredLanguageCodes.push(languageCodes[i]);
seen[languageCodes[i]] = true;
}
}
return filteredLanguageCodes;
},
};
return {
LanguageList: LanguageList,
LanguageListItem: LanguageListItem
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(kochi): Generalize the notification as a component and put it
// in js/cr/ui/notification.js .
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/** @const */ var LanguageList = options.LanguageList;
// Some input methods like Chinese Pinyin have config pages.
// This is the map of the input method names to their config page names.
/** @const */ var INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = {
'mozc': 'languageMozc',
'mozc-chewing': 'languageChewing',
'mozc-dv': 'languageMozc',
'mozc-hangul': 'languageHangul',
'mozc-jp': 'languageMozc',
'pinyin': 'languagePinyin',
'pinyin-dv': 'languagePinyin',
};
/////////////////////////////////////////////////////////////////////////////
// LanguageOptions class:
/**
* Encapsulated handling of ChromeOS language options page.
* @constructor
*/
function LanguageOptions(model) {
OptionsPage.call(this, 'languages',
loadTimeData.getString('languagePageTabTitle'),
'languagePage');
}
cr.addSingletonGetter(LanguageOptions);
// Inherit LanguageOptions from OptionsPage.
LanguageOptions.prototype = {
__proto__: OptionsPage.prototype,
/* For recording the prospective language (the next locale after relaunch).
* @type {?string}
* @private
*/
prospectiveUiLanguageCode_: null,
/**
* Initializes LanguageOptions page.
* Calls base class implementation to start preference initialization.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var languageOptionsList = $('language-options-list');
LanguageList.decorate(languageOptionsList);
languageOptionsList.addEventListener('change',
this.handleLanguageOptionsListChange_.bind(this));
languageOptionsList.addEventListener('save',
this.handleLanguageOptionsListSave_.bind(this));
this.prospectiveUiLanguageCode_ =
loadTimeData.getString('prospectiveUiLanguageCode');
this.addEventListener('visibleChange',
this.handleVisibleChange_.bind(this));
if (cr.isChromeOS) {
$('chewing-confirm').onclick = $('hangul-confirm').onclick =
$('mozc-confirm').onclick = $('pinyin-confirm').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
this.initializeInputMethodList_();
this.initializeLanguageCodeToInputMethodIdsMap_();
}
Preferences.getInstance().addEventListener(this.spellCheckDictionaryPref,
this.handleSpellCheckDictionaryPrefChange_.bind(this));
// Set up add button.
$('language-options-add-button').onclick = function(e) {
// Add the language without showing the overlay if it's specified in
// the URL hash (ex. lang_add=ja). Used for automated testing.
var match = document.location.hash.match(/\blang_add=([\w-]+)/);
if (match) {
var addLanguageCode = match[1];
$('language-options-list').addLanguage(addLanguageCode);
} else {
OptionsPage.navigateToPage('addLanguage');
}
};
if (!cr.isMac) {
// Set up the button for editing custom spelling dictionary.
$('edit-dictionary-button') .onclick = function(e) {
OptionsPage.navigateToPage('editDictionary');
};
}
if (cr.isChromeOS) {
// Listen to user clicks on the add language list.
var addLanguageList = $('add-language-overlay-language-list');
addLanguageList.addEventListener(
'click',
this.handleAddLanguageListClick_.bind(this));
$('language-options-extension-ime-button').addEventListener(
'click',
this.handleExtensionImeButtonClick_.bind(this));
} else {
// Listen to add language dialog ok button.
var addLanguageOkButton = $('add-language-overlay-ok-button');
addLanguageOkButton.addEventListener(
'click',
this.handleAddLanguageOkButtonClick_.bind(this));
// Show experimental features if enabled.
if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
$('auto-spell-correction-option').hidden = false;
// Handle spell check enable/disable.
if (!cr.isMac) {
Preferences.getInstance().addEventListener(
this.enableSpellCheckPref,
this.updateEnableSpellCheck_.bind(this));
}
}
// Handle clicks on "Use this language for spell checking" button.
if (!cr.isMac) {
var spellCheckLanguageButton = getRequiredElement(
'language-options-spell-check-language-button');
spellCheckLanguageButton.addEventListener(
'click',
this.handleSpellCheckLanguageButtonClick_.bind(this));
}
if (cr.isChromeOS) {
$('language-options-ui-restart-button').onclick = function() {
chrome.send('uiLanguageRestart');
};
}
$('language-confirm').onclick =
OptionsPage.closeOverlay.bind(OptionsPage);
},
// The preference is a boolean that enables/disables spell checking.
enableSpellCheckPref: 'browser.enable_spellchecking',
// The preference is a CSV string that describes preload engines
// (i.e. active input methods).
preloadEnginesPref: 'settings.language.preload_engines',
// The list of preload engines, like ['mozc', 'pinyin'].
preloadEngines_: [],
// The preference that lists the extension IMEs that are filtered out of
// the language menu.
filteredExtensionImesPref: 'settings.language.filtered_extension_imes',
// The list of extension IMEs that are filtered out of the language menu.
filteredExtensionImes_: [],
// The preference is a string that describes the spell check
// dictionary language, like "en-US".
spellCheckDictionaryPref: 'spellcheck.dictionary',
spellCheckDictionary_: '',
// The map of language code to input method IDs, like:
// {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
languageCodeToInputMethodIdsMap_: {},
/**
* Initializes the input method list.
*/
initializeInputMethodList_: function() {
var inputMethodList = $('language-options-input-method-list');
var inputMethodListData = loadTimeData.getValue('inputMethodList');
var inputMethodPrototype = $('language-options-input-method-proto');
// Add all input methods, but make all of them invisible here. We'll
// change the visibility in handleLanguageOptionsListChange_() based
// on the selected language. Note that we only have less than 100
// input methods, so creating DOM nodes at once here should be ok.
for (var i = 0; i < inputMethodListData.length; i++) {
var inputMethod = inputMethodListData[i];
var element = inputMethodPrototype.cloneNode(true);
element.id = '';
element.languageCodeSet = inputMethod.languageCodeSet;
var input = element.querySelectorAll('input')[0];
input.inputMethodId = inputMethod.id;
var span = element.querySelectorAll('span')[0];
span.textContent = inputMethod.displayName;
// Listen to user clicks.
input.addEventListener('click',
this.handleCheckboxClick_.bind(this));
// Add the configure button if the config page is present for this
// input method.
if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) {
var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id];
var button = this.createConfigureInputMethodButton_(inputMethod.id,
pageName);
element.appendChild(button);
}
inputMethodList.appendChild(element);
}
var extensionImeList = loadTimeData.getValue('extensionImeList');
for (var i = 0; i < extensionImeList.length; i++) {
var inputMethod = extensionImeList[i];
var element = inputMethodPrototype.cloneNode(true);
element.id = '';
element.languageCodeSet = {};
var input = element.querySelectorAll('input')[0];
input.inputMethodId = inputMethod.id;
var span = element.querySelectorAll('span')[0];
span.textContent = inputMethod.displayName;
input.addEventListener('click',
this.handleExtensionCheckboxClick_.bind(this));
inputMethodList.appendChild(element);
}
// Listen to pref change once the input method list is initialized.
Preferences.getInstance().addEventListener(
this.preloadEnginesPref,
this.handlePreloadEnginesPrefChange_.bind(this));
Preferences.getInstance().addEventListener(
this.filteredExtensionImesPref,
this.handleFilteredExtensionsPrefChange_.bind(this));
},
/**
* Creates a configure button for the given input method ID.
* @param {string} inputMethodId Input method ID (ex. "pinyin").
* @param {string} pageName Name of the config page (ex. "languagePinyin").
* @private
*/
createConfigureInputMethodButton_: function(inputMethodId, pageName) {
var button = document.createElement('button');
button.textContent = loadTimeData.getString('configure');
button.onclick = function(e) {
// Prevent the default action (i.e. changing the checked property
// of the checkbox). The button click here should not be handled
// as checkbox click.
e.preventDefault();
chrome.send('inputMethodOptionsOpen', [inputMethodId]);
OptionsPage.navigateToPage(pageName);
};
return button;
},
/**
* Handles OptionsPage's visible property change event.
* @param {Event} e Property change event.
* @private
*/
handleVisibleChange_: function(e) {
if (this.visible) {
$('language-options-list').redraw();
chrome.send('languageOptionsOpen');
}
},
/**
* Handles languageOptionsList's change event.
* @param {Event} e Change event.
* @private
*/
handleLanguageOptionsListChange_: function(e) {
var languageOptionsList = $('language-options-list');
var languageCode = languageOptionsList.getSelectedLanguageCode();
// If there's no selection, just return.
if (!languageCode)
return;
// Select the language if it's specified in the URL hash (ex. lang=ja).
// Used for automated testing.
var match = document.location.hash.match(/\blang=([\w-]+)/);
if (match) {
var specifiedLanguageCode = match[1];
if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
languageCode = specifiedLanguageCode;
}
}
if (cr.isWindows || cr.isChromeOS)
this.updateUiLanguageButton_(languageCode);
if (!cr.isMac) {
this.updateSelectedLanguageName_(languageCode);
this.updateSpellCheckLanguageButton_(languageCode);
}
if (cr.isChromeOS)
this.updateInputMethodList_(languageCode);
this.updateLanguageListInAddLanguageOverlay_();
},
/**
* Happens when a user changes back to the language they're currently using.
*/
currentLocaleWasReselected: function() {
this.updateUiLanguageButton_(
loadTimeData.getString('currentUiLanguageCode'));
},
/**
* Handles languageOptionsList's save event.
* @param {Event} e Save event.
* @private
*/
handleLanguageOptionsListSave_: function(e) {
if (cr.isChromeOS) {
// Sort the preload engines per the saved languages before save.
this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
this.savePreloadEnginesPref_();
}
},
/**
* Sorts preloadEngines_ by languageOptionsList's order.
* @param {Array} preloadEngines List of preload engines.
* @return {Array} Returns sorted preloadEngines.
* @private
*/
sortPreloadEngines_: function(preloadEngines) {
// For instance, suppose we have two languages and associated input
// methods:
//
// - Korean: hangul
// - Chinese: pinyin
//
// The preloadEngines preference should look like "hangul,pinyin".
// If the user reverse the order, the preference should be reorderd
// to "pinyin,hangul".
var languageOptionsList = $('language-options-list');
var languageCodes = languageOptionsList.getLanguageCodes();
// Convert the list into a dictonary for simpler lookup.
var preloadEngineSet = {};
for (var i = 0; i < preloadEngines.length; i++) {
preloadEngineSet[preloadEngines[i]] = true;
}
// Create the new preload engine list per the language codes.
var newPreloadEngines = [];
for (var i = 0; i < languageCodes.length; i++) {
var languageCode = languageCodes[i];
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
languageCode];
// Check if we have active input methods associated with the language.
for (var j = 0; j < inputMethodIds.length; j++) {
var inputMethodId = inputMethodIds[j];
if (inputMethodId in preloadEngineSet) {
// If we have, add it to the new engine list.
newPreloadEngines.push(inputMethodId);
// And delete it from the set. This is necessary as one input
// method can be associated with more than one language thus
// we should avoid having duplicates in the new list.
delete preloadEngineSet[inputMethodId];
}
}
}
return newPreloadEngines;
},
/**
* Initializes the map of language code to input method IDs.
* @private
*/
initializeLanguageCodeToInputMethodIdsMap_: function() {
var inputMethodList = loadTimeData.getValue('inputMethodList');
for (var i = 0; i < inputMethodList.length; i++) {
var inputMethod = inputMethodList[i];
for (var languageCode in inputMethod.languageCodeSet) {
if (languageCode in this.languageCodeToInputMethodIdsMap_) {
this.languageCodeToInputMethodIdsMap_[languageCode].push(
inputMethod.id);
} else {
this.languageCodeToInputMethodIdsMap_[languageCode] =
[inputMethod.id];
}
}
}
},
/**
* Updates the currently selected language name.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateSelectedLanguageName_: function(languageCode) {
var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
languageCode);
var languageDisplayName = languageInfo.displayName;
var languageNativeDisplayName = languageInfo.nativeDisplayName;
var textDirection = languageInfo.textDirection;
// If the native name is different, add it.
if (languageDisplayName != languageNativeDisplayName) {
languageDisplayName += ' - ' + languageNativeDisplayName;
}
// Update the currently selected language name.
var languageName = $('language-options-language-name');
languageName.textContent = languageDisplayName;
languageName.dir = textDirection;
},
/**
* Updates the UI language button.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateUiLanguageButton_: function(languageCode) {
var uiLanguageButton = $('language-options-ui-language-button');
var uiLanguageMessage = $('language-options-ui-language-message');
var uiLanguageNotification = $('language-options-ui-notification-bar');
// Remove the event listener and add it back if useful.
uiLanguageButton.onclick = null;
// Unhide the language button every time, as it could've been previously
// hidden by a language change.
uiLanguageButton.hidden = false;
if (languageCode == this.prospectiveUiLanguageCode_) {
uiLanguageMessage.textContent =
loadTimeData.getString('is_displayed_in_this_language');
showMutuallyExclusiveNodes(
[uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
} else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
// In the guest mode for ChromeOS, changing UI language does not make
// sense because it does not take effect after browser restart.
uiLanguageButton.hidden = true;
uiLanguageMessage.hidden = true;
} else {
uiLanguageButton.textContent =
loadTimeData.getString('display_in_this_language');
showMutuallyExclusiveNodes(
[uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
uiLanguageButton.onclick = function(e) {
chrome.send('uiLanguageChange', [languageCode]);
};
}
} else {
uiLanguageMessage.textContent =
loadTimeData.getString('cannot_be_displayed_in_this_language');
showMutuallyExclusiveNodes(
[uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
}
},
/**
* Updates the spell check language button.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateSpellCheckLanguageButton_: function(languageCode) {
var spellCheckLanguageButton =
$('language-options-spell-check-language-button');
var spellCheckLanguageMessage =
$('language-options-spell-check-language-message');
if (languageCode == this.spellCheckDictionary_) {
spellCheckLanguageMessage.textContent =
loadTimeData.getString('is_used_for_spell_checking');
showMutuallyExclusiveNodes(
[spellCheckLanguageButton, spellCheckLanguageMessage], 1);
} else if (languageCode in
loadTimeData.getValue('spellCheckLanguageCodeSet')) {
spellCheckLanguageButton.textContent =
loadTimeData.getString('use_this_for_spell_checking');
showMutuallyExclusiveNodes(
[spellCheckLanguageButton, spellCheckLanguageMessage], 0);
spellCheckLanguageButton.languageCode = languageCode;
} else if (!languageCode) {
spellCheckLanguageButton.hidden = true;
spellCheckLanguageMessage.hidden = true;
} else {
spellCheckLanguageMessage.textContent =
loadTimeData.getString('cannot_be_used_for_spell_checking');
showMutuallyExclusiveNodes(
[spellCheckLanguageButton, spellCheckLanguageMessage], 1);
}
},
/**
* Updates the input method list.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateInputMethodList_: function(languageCode) {
// Give one of the checkboxes or buttons focus, if it's specified in the
// URL hash (ex. focus=mozc). Used for automated testing.
var focusInputMethodId = -1;
var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
if (match) {
focusInputMethodId = match[1];
}
// Change the visibility of the input method list. Input methods that
// matches |languageCode| will become visible.
var inputMethodList = $('language-options-input-method-list');
var methods = inputMethodList.querySelectorAll('.input-method');
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
if (languageCode in method.languageCodeSet) {
method.hidden = false;
var input = method.querySelectorAll('input')[0];
// Give it focus if the ID matches.
if (input.inputMethodId == focusInputMethodId) {
input.focus();
}
} else {
method.hidden = true;
}
}
if (focusInputMethodId == 'add') {
$('language-options-add-button').focus();
}
},
/**
* Updates the language list in the add language overlay.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateLanguageListInAddLanguageOverlay_: function(languageCode) {
// Change the visibility of the language list in the add language
// overlay. Languages that are already active will become invisible,
// so that users don't add the same language twice.
var languageOptionsList = $('language-options-list');
var languageCodes = languageOptionsList.getLanguageCodes();
var languageCodeSet = {};
for (var i = 0; i < languageCodes.length; i++) {
languageCodeSet[languageCodes[i]] = true;
}
var addLanguageList = $('add-language-overlay-language-list');
var lis = addLanguageList.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
// The first child button knows the language code.
var button = lis[i].childNodes[0];
if (button.languageCode in languageCodeSet) {
lis[i].style.display = 'none';
} else {
lis[i].style.display = 'block';
}
}
},
/**
* Handles preloadEnginesPref change.
* @param {Event} e Change event.
* @private
*/
handlePreloadEnginesPrefChange_: function(e) {
var value = e.value.value;
this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
this.updateCheckboxesFromPreloadEngines_();
$('language-options-list').updateDeletable();
},
/**
* Handles filteredExtensionImesPref change.
* @param {Event} e Change event.
* @private
*/
handleFilteredExtensionsPrefChange_: function(e) {
var value = e.value.value;
this.filteredExtensionImes_ = value.split(',');
this.updateCheckboxesFromFilteredExtensions_();
},
/**
* Handles input method checkbox's click event.
* @param {Event} e Click event.
* @private
*/
handleCheckboxClick_: function(e) {
var checkbox = e.target;
if (this.preloadEngines_.length == 1 && !checkbox.checked) {
// Don't allow disabling the last input method.
this.showNotification_(
loadTimeData.getString('please_add_another_input_method'),
loadTimeData.getString('ok_button'));
checkbox.checked = true;
return;
}
if (checkbox.checked) {
chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
} else {
chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
}
this.updatePreloadEnginesFromCheckboxes_();
this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
this.savePreloadEnginesPref_();
},
/**
* Handles extension input method checkbox's click event.
* @param {Event} e Click event.
* @private
*/
handleExtensionCheckboxClick_: function(e) {
var checkbox = e.target;
this.updateFilteredExtensionsFromCheckboxes_();
this.saveFilteredExtensionPref_();
},
/**
* Handles add language list's click event.
* @param {Event} e Click event.
*/
handleAddLanguageListClick_: function(e) {
var languageOptionsList = $('language-options-list');
var languageCode = e.target.languageCode;
// languageCode can be undefined, if click was made on some random
// place in the overlay, rather than a button. Ignore it.
if (!languageCode) {
return;
}
languageOptionsList.addLanguage(languageCode);
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
// Enable the first input method for the language added.
if (inputMethodIds && inputMethodIds[0] &&
// Don't add the input method it's already present. This can
// happen if the same input method is shared among multiple
// languages (ex. English US keyboard is used for English US and
// Filipino).
this.preloadEngines_.indexOf(inputMethodIds[0]) == -1) {
this.preloadEngines_.push(inputMethodIds[0]);
this.updateCheckboxesFromPreloadEngines_();
this.savePreloadEnginesPref_();
}
OptionsPage.closeOverlay();
},
/**
* Handles extension IME button.
*/
handleExtensionImeButtonClick_: function() {
$('language-options-list').clearSelection();
var languageName = $('language-options-language-name');
languageName.textContent = loadTimeData.getString('extension_ime_label');
var uiLanguageMessage = $('language-options-ui-language-message');
uiLanguageMessage.textContent =
loadTimeData.getString('extension_ime_description');
var uiLanguageButton = $('language-options-ui-language-button');
uiLanguageButton.onclick = null;
uiLanguageButton.hidden = true;
this.updateSpellCheckLanguageButton_();
// Hide all input method checkboxes that aren't extension IMEs.
var inputMethodList = $('language-options-input-method-list');
var methods = inputMethodList.querySelectorAll('.input-method');
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
var input = method.querySelectorAll('input')[0];
// Give it focus if the ID matches.
if (input.inputMethodId.match(/^_ext_ime_/))
method.hidden = false;
else
method.hidden = true;
}
},
handleAddLanguageOkButtonClick_: function() {
var languagesSelect = $('add-language-overlay-language-list');
var selectedIndex = languagesSelect.selectedIndex;
if (selectedIndex >= 0) {
var selection = languagesSelect.options[selectedIndex];
$('language-options-list').addLanguage(String(selection.value));
OptionsPage.closeOverlay();
}
},
/**
* Checks if languageCode is deletable or not.
* @param {String} languageCode the languageCode to check for deletability.
*/
languageIsDeletable: function(languageCode) {
// Don't allow removing the language if it's a UI language.
if (languageCode == this.prospectiveUiLanguageCode_)
return false;
return (!cr.isChromeOS ||
this.canDeleteLanguage_(languageCode));
},
/**
* Handles browse.enable_spellchecking change.
* @param {Event} e Change event.
* @private
*/
updateEnableSpellCheck_: function() {
var value = !$('enable-spell-check').checked;
$('language-options-spell-check-language-button').disabled = value;
if (!cr.IsMac)
$('edit-dictionary-button').hidden = value;
},
/**
* Handles spellCheckDictionaryPref change.
* @param {Event} e Change event.
* @private
*/
handleSpellCheckDictionaryPrefChange_: function(e) {
var languageCode = e.value.value;
this.spellCheckDictionary_ = languageCode;
var languageOptionsList = $('language-options-list');
var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
if (!cr.isMac)
this.updateSpellCheckLanguageButton_(selectedLanguageCode);
},
/**
* Handles spellCheckLanguageButton click.
* @param {Event} e Click event.
* @private
*/
handleSpellCheckLanguageButtonClick_: function(e) {
var languageCode = e.target.languageCode;
// Save the preference.
Preferences.setStringPref(this.spellCheckDictionaryPref,
languageCode, true);
chrome.send('spellCheckLanguageChange', [languageCode]);
},
/**
* Checks whether it's possible to remove the language specified by
* languageCode and returns true if possible. This function returns false
* if the removal causes the number of preload engines to be zero.
*
* @param {string} languageCode Language code (ex. "fr").
* @return {boolean} Returns true on success.
* @private
*/
canDeleteLanguage_: function(languageCode) {
// First create the set of engines to be removed from input methods
// associated with the language code.
var enginesToBeRemovedSet = {};
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
for (var i = 0; i < inputMethodIds.length; i++) {
enginesToBeRemovedSet[inputMethodIds[i]] = true;
}
// Then eliminate engines that are also used for other active languages.
// For instance, if "xkb:us::eng" is used for both English and Filipino.
var languageCodes = $('language-options-list').getLanguageCodes();
for (var i = 0; i < languageCodes.length; i++) {
// Skip the target language code.
if (languageCodes[i] == languageCode) {
continue;
}
// Check if input methods used in this language are included in
// enginesToBeRemovedSet. If so, eliminate these from the set, so
// we don't remove this time.
var inputMethodIdsForAnotherLanguage =
this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
var inputMethodId = inputMethodIdsForAnotherLanguage[j];
if (inputMethodId in enginesToBeRemovedSet) {
delete enginesToBeRemovedSet[inputMethodId];
}
}
}
// Update the preload engine list with the to-be-removed set.
var newPreloadEngines = [];
for (var i = 0; i < this.preloadEngines_.length; i++) {
if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
newPreloadEngines.push(this.preloadEngines_[i]);
}
}
// Don't allow this operation if it causes the number of preload
// engines to be zero.
return (newPreloadEngines.length > 0);
},
/**
* Saves the filtered extension preference.
* @private
*/
saveFilteredExtensionPref_: function() {
Preferences.setStringPref(this.filteredExtensionImesPref,
this.filteredExtensionImes_.join(','), true);
},
/**
* Updates the checkboxes in the input method list from the filtered
* extensions preference.
* @private
*/
updateCheckboxesFromFilteredExtensions_: function() {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
for (var i = 0; i < this.filteredExtensionImes_.length; i++)
dictionary[this.filteredExtensionImes_[i]] = true;
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
checkboxes[i].checked = !(checkboxes[i].inputMethodId in dictionary);
}
},
/**
* Updates the filtered extensions preference from the checkboxes in the
* input method list.
* @private
*/
updateFilteredExtensionsFromCheckboxes_: function() {
this.filteredExtensionImes_ = [];
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
if (!checkboxes[i].checked)
this.filteredExtensionImes_.push(checkboxes[i].inputMethodId);
}
}
},
/**
* Saves the preload engines preference.
* @private
*/
savePreloadEnginesPref_: function() {
Preferences.setStringPref(this.preloadEnginesPref,
this.preloadEngines_.join(','), true);
},
/**
* Updates the checkboxes in the input method list from the preload
* engines preference.
* @private
*/
updateCheckboxesFromPreloadEngines_: function() {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
for (var i = 0; i < this.preloadEngines_.length; i++) {
dictionary[this.preloadEngines_[i]] = true;
}
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
}
},
/**
* Updates the preload engines preference from the checkboxes in the
* input method list.
* @private
*/
updatePreloadEnginesFromCheckboxes_: function() {
this.preloadEngines_ = [];
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
if (checkboxes[i].checked)
this.preloadEngines_.push(checkboxes[i].inputMethodId);
}
}
var languageOptionsList = $('language-options-list');
languageOptionsList.updateDeletable();
},
/**
* Filters bad preload engines in case bad preload engines are
* stored in the preference. Removes duplicates as well.
* @param {Array} preloadEngines List of preload engines.
* @private
*/
filterBadPreloadEngines_: function(preloadEngines) {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
var list = loadTimeData.getValue('inputMethodList');
for (var i = 0; i < list.length; i++) {
dictionary[list[i].id] = true;
}
var filteredPreloadEngines = [];
var seen = {};
for (var i = 0; i < preloadEngines.length; i++) {
// Check if the preload engine is present in the
// dictionary, and not duplicate. Otherwise, skip it.
if (preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) {
filteredPreloadEngines.push(preloadEngines[i]);
seen[preloadEngines[i]] = true;
}
}
return filteredPreloadEngines;
},
// TODO(kochi): This is an adapted copy from new_tab.js.
// If this will go as final UI, refactor this to share the component with
// new new tab page.
/**
* Shows notification
* @private
*/
notificationTimeout_: null,
showNotification_: function(text, actionText, opt_delay) {
var notificationElement = $('notification');
var actionLink = notificationElement.querySelector('.link-color');
var delay = opt_delay || 10000;
function show() {
window.clearTimeout(this.notificationTimeout_);
notificationElement.classList.add('show');
document.body.classList.add('notification-shown');
}
function hide() {
window.clearTimeout(this.notificationTimeout_);
notificationElement.classList.remove('show');
document.body.classList.remove('notification-shown');
// Prevent tabbing to the hidden link.
actionLink.tabIndex = -1;
// Setting tabIndex to -1 only prevents future tabbing to it. If,
// however, the user switches window or a tab and then moves back to
// this tab the element may gain focus. We therefore make sure that we
// blur the element so that the element focus is not restored when
// coming back to this window.
actionLink.blur();
}
function delayedHide() {
this.notificationTimeout_ = window.setTimeout(hide, delay);
}
notificationElement.firstElementChild.textContent = text;
actionLink.textContent = actionText;
actionLink.onclick = hide;
actionLink.onkeydown = function(e) {
if (e.keyIdentifier == 'Enter') {
hide();
}
};
notificationElement.onmouseover = show;
notificationElement.onmouseout = delayedHide;
actionLink.onfocus = show;
actionLink.onblur = delayedHide;
// Enable tabbing to the link now that it is shown.
actionLink.tabIndex = 0;
show();
delayedHide();
}
};
/**
* Shows the node at |index| in |nodes|, hides all others.
* @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
* @param {number} index The index of |nodes| to show.
*/
function showMutuallyExclusiveNodes(nodes, index) {
assert(index >= 0 && index < nodes.length);
for (var i = 0; i < nodes.length; ++i) {
assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
nodes[i].hidden = i != index;
}
}
/**
* Chrome callback for when the UI language preference is saved.
* @param {string} languageCode The newly selected language to use.
*/
LanguageOptions.uiLanguageSaved = function(languageCode) {
this.prospectiveUiLanguageCode_ = languageCode;
// If the user is no longer on the same language code, ignore.
if ($('language-options-list').getSelectedLanguageCode() != languageCode)
return;
// Special case for when a user changes to a different language, and changes
// back to the same language without having restarted Chrome or logged
// in/out of ChromeOS.
if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
LanguageOptions.getInstance().currentLocaleWasReselected();
return;
}
// Otherwise, show a notification telling the user that their changes will
// only take effect after restart.
showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
$('language-options-ui-notification-bar')], 1);
};
// Export
return {
LanguageOptions: LanguageOptions
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var ArrayDataModel = cr.ui.ArrayDataModel;
/**
* ManageProfileOverlay class
* Encapsulated handling of the 'Manage profile...' overlay page.
* @constructor
* @class
*/
function ManageProfileOverlay() {
OptionsPage.call(this, 'manageProfile',
loadTimeData.getString('manageProfileTabTitle'),
'manage-profile-overlay');
};
cr.addSingletonGetter(ManageProfileOverlay);
ManageProfileOverlay.prototype = {
// Inherit from OptionsPage.
__proto__: OptionsPage.prototype,
// Info about the currently managed/deleted profile.
profileInfo_: null,
// An object containing all known profile names.
profileNames_: {},
// The currently selected icon in the icon grid.
iconGridSelectedURL_: null,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
var iconGrid = $('manage-profile-icon-grid');
var createIconGrid = $('create-profile-icon-grid');
options.ProfilesIconGrid.decorate(iconGrid);
options.ProfilesIconGrid.decorate(createIconGrid);
iconGrid.addEventListener('change', function(e) {
self.onIconGridSelectionChanged_('manage');
});
createIconGrid.addEventListener('change', function(e) {
self.onIconGridSelectionChanged_('create');
});
$('manage-profile-name').oninput = function(event) {
self.onNameChanged_(event, 'manage');
};
$('create-profile-name').oninput = function(event) {
self.onNameChanged_(event, 'create');
};
$('manage-profile-cancel').onclick =
$('delete-profile-cancel').onclick =
$('create-profile-cancel').onclick = function(event) {
OptionsPage.closeOverlay();
};
$('manage-profile-ok').onclick = function(event) {
OptionsPage.closeOverlay();
self.submitManageChanges_();
};
$('delete-profile-ok').onclick = function(event) {
OptionsPage.closeOverlay();
chrome.send('deleteProfile', [self.profileInfo_.filePath]);
};
$('create-profile-ok').onclick = function(event) {
OptionsPage.closeOverlay();
// Get the user's chosen name and icon, or default if they do not
// wish to customize their profile.
var name = $('create-profile-name').value;
var icon_url = createIconGrid.selectedItem;
var create_checkbox = false;
if ($('create-shortcut'))
create_checkbox = $('create-shortcut').checked;
chrome.send('createProfile', [name, icon_url, create_checkbox]);
};
},
/** @override */
didShowPage: function() {
chrome.send('requestDefaultProfileIcons');
if ($('create-shortcut'))
$('create-shortcut').checked = true;
if ($('manage-shortcut'))
$('manage-shortcut').checked = false;
// Just ignore the manage profile dialog on Chrome OS, they use /accounts.
if (!cr.isChromeOS && window.location.pathname == '/manageProfile')
ManageProfileOverlay.getInstance().prepareForManageDialog_();
$('manage-profile-name').focus();
$('create-profile-name').focus();
},
/**
* Set the profile info used in the dialog.
* @param {Object} profileInfo An object of the form:
* profileInfo = {
* name: "Profile Name",
* iconURL: "chrome://path/to/icon/image",
* filePath: "/path/to/profile/data/on/disk"
* isCurrentProfile: false,
* };
* @param {String} mode A label that specifies the type of dialog
* box which is currently being viewed (i.e. 'create' or
* 'manage').
* @private
*/
setProfileInfo_: function(profileInfo, mode) {
this.iconGridSelectedURL_ = profileInfo.iconURL;
this.profileInfo_ = profileInfo;
$(mode + '-profile-name').value = profileInfo.name;
$(mode + '-profile-icon-grid').selectedItem = profileInfo.iconURL;
},
/**
* Sets the name of the currently edited profile.
* @private
*/
setProfileName_: function(name) {
if (this.profileInfo_)
this.profileInfo_.name = name;
$('manage-profile-name').value = name;
},
/**
* the user will use to choose their profile icon.
* @param {Array.<string>} iconURLs An array of icon URLs.
* @private
*/
receiveDefaultProfileIcons_: function(iconGrid, iconURLs) {
$(iconGrid).dataModel = new ArrayDataModel(iconURLs);
if (this.profileInfo_)
$(iconGrid).selectedItem = this.profileInfo_.iconURL;
var grid = $(iconGrid);
// Recalculate the measured item size.
grid.measured_ = null;
grid.columns = 0;
grid.redraw();
},
/**
* Set a dictionary of all profile names. These are used to prevent the
* user from naming two profiles the same.
* @param {Object} profileNames A dictionary of profile names.
* @private
*/
receiveProfileNames_: function(profileNames) {
this.profileNames_ = profileNames;
},
/**
* Display the error bubble, with |errorText| in the bubble.
* @param {string} errorText The localized string id to display as an error.
* @param {String} mode A label that specifies the type of dialog
* box which is currently being viewed (i.e. 'create' or
* 'manage').
* @private
*/
showErrorBubble_: function(errorText, mode) {
var nameErrorEl = $(mode + '-profile-error-bubble');
nameErrorEl.hidden = false;
nameErrorEl.textContent = loadTimeData.getString(errorText);
$(mode + '-profile-ok').disabled = true;
},
/**
* Hide the error bubble.
* @param {String} mode A label that specifies the type of dialog
* box which is currently being viewed (i.e. 'create' or
* 'manage').
* @private
*/
hideErrorBubble_: function(mode) {
$(mode + '-profile-error-bubble').hidden = true;
$(mode + '-profile-ok').disabled = false;
},
/**
* oninput callback for <input> field.
* @param {Event} event The event object.
* @param {String} mode A label that specifies the type of dialog
* box which is currently being viewed (i.e. 'create' or
* 'manage').
* @private
*/
onNameChanged_: function(event, mode) {
var newName = event.target.value;
var oldName = this.profileInfo_.name;
if (newName == oldName) {
this.hideErrorBubble_(mode);
} else if (this.profileNames_[newName] != undefined) {
this.showErrorBubble_('manageProfilesDuplicateNameError', mode);
} else {
this.hideErrorBubble_(mode);
var nameIsValid = $(mode + '-profile-name').validity.valid;
$(mode + '-profile-ok').disabled = !nameIsValid;
}
},
/**
* Called when the user clicks "OK". Saves the newly changed profile info.
* @private
*/
submitManageChanges_: function() {
var name = $('manage-profile-name').value;
var iconURL = $('manage-profile-icon-grid').selectedItem;
var manage_checkbox = false;
if ($('manage-shortcut'))
manage_checkbox = $('manage-shortcut').checked;
chrome.send('setProfileNameAndIcon',
[this.profileInfo_.filePath, name, iconURL,
manage_checkbox]);
},
/**
* Called when the selected icon in the icon grid changes.
* @param {String} mode A label that specifies the type of dialog
* box which is currently being viewed (i.e. 'create' or
* 'manage').
* @private
*/
onIconGridSelectionChanged_: function(mode) {
var iconURL = $(mode + '-profile-icon-grid').selectedItem;
if (!iconURL || iconURL == this.iconGridSelectedURL_)
return;
this.iconGridSelectedURL_ = iconURL;
chrome.send('profileIconSelectionChanged',
[this.profileInfo_.filePath, iconURL]);
},
/**
* Updates the contents of the "Manage Profile" section of the dialog,
* and shows that section.
* @private
*/
prepareForManageDialog_: function() {
var profileInfo = BrowserOptions.getCurrentProfile();
ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
$('manage-profile-overlay-create').hidden = true;
$('manage-profile-overlay-manage').hidden = false;
$('manage-profile-overlay-delete').hidden = true;
this.hideErrorBubble_('manage');
},
/**
* Display the "Manage Profile" dialog.
* @private
*/
showManageDialog_: function() {
this.prepareForManageDialog_();
OptionsPage.navigateToPage('manageProfile');
},
/**
* Display the "Delete Profile" dialog.
* @param {Object} profileInfo The profile object of the profile to delete.
* @private
*/
showDeleteDialog_: function(profileInfo) {
ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
$('manage-profile-overlay-create').hidden = true;
$('manage-profile-overlay-manage').hidden = true;
$('manage-profile-overlay-delete').hidden = false;
$('delete-profile-message').textContent =
loadTimeData.getStringF('deleteProfileMessage', profileInfo.name);
$('delete-profile-message').style.backgroundImage = 'url("' +
profileInfo.iconURL + '")';
// Because this dialog isn't useful when refreshing or as part of the
// history, don't create a history entry for it when showing.
OptionsPage.showPageByName('manageProfile', false);
},
/**
* Display the "Create Profile" dialog.
* @param {Object} profileInfo The profile object of the profile to
* create. Upon creation, this object only needs a name and an avatar.
* @private
*/
showCreateDialog_: function(profileInfo) {
ManageProfileOverlay.setProfileInfo(profileInfo, 'create');
$('manage-profile-overlay-create').hidden = false;
$('manage-profile-overlay-manage').hidden = true;
$('manage-profile-overlay-delete').hidden = true;
$('create-profile-instructions').textContent =
loadTimeData.getStringF('createProfileInstructions');
ManageProfileOverlay.getInstance().hideErrorBubble_('create');
OptionsPage.showPageByName('manageProfile', false);
},
};
// Forward public APIs to private implementations.
[
'receiveDefaultProfileIcons',
'receiveProfileNames',
'setProfileInfo',
'setProfileName',
'showManageDialog',
'showDeleteDialog',
'showCreateDialog',
].forEach(function(name) {
ManageProfileOverlay[name] = function() {
var instance = ManageProfileOverlay.getInstance();
return instance[name + '_'].apply(instance, arguments);
};
});
// Export
return {
ManageProfileOverlay: ManageProfileOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var DeletableItemList = options.DeletableItemList;
/**
* @constructor
* @extends {DeletableItem}
*/
function MediaGalleriesListItem(galleryInfo) {
var el = cr.doc.createElement('div');
el.galleryInfo_ = galleryInfo;
el.__proto__ = MediaGalleriesListItem.prototype;
el.decorate();
return el;
}
MediaGalleriesListItem.prototype = {
__proto__: DeletableItem.prototype,
decorate: function() {
DeletableItem.prototype.decorate.call(this);
var span = this.ownerDocument.createElement('span');
span.textContent = this.galleryInfo_.displayName;
this.contentElement.appendChild(span);
this.contentElement.title = this.galleryInfo_.path;
},
};
var MediaGalleriesList = cr.ui.define('list');
MediaGalleriesList.prototype = {
__proto__: DeletableItemList.prototype,
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.autoExpands_ = true;
},
/** @override */
createItem: function(galleryInfo) {
return new MediaGalleriesListItem(galleryInfo);
},
/** @override */
deleteItemAtIndex: function(index) {
chrome.send('forgetGallery', [this.dataModel.item(index).id]);
},
};
return {
MediaGalleriesList: MediaGalleriesList
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var OptionsPage = options.OptionsPage;
/**
* This class is an overlay which allows the user to add or remove media
* galleries, and displays known media galleries.
* @constructor
* @extends {OptionsPage}
*/
function MediaGalleriesManager() {
OptionsPage.call(this, 'manageGalleries',
loadTimeData.getString('manageMediaGalleriesTabTitle'),
'manage-media-galleries-overlay');
}
cr.addSingletonGetter(MediaGalleriesManager);
MediaGalleriesManager.prototype = {
__proto__: OptionsPage.prototype,
/**
* Decorate the overlay and set up event handlers.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.availableGalleriesList_ = $('available-galleries-list');
options.MediaGalleriesList.decorate(this.availableGalleriesList_);
$('new-media-gallery').addEventListener('click', function() {
chrome.send('addNewGallery');
});
$('manage-media-confirm').addEventListener(
'click', OptionsPage.closeOverlay.bind(OptionsPage));
this.addEventListener('visibleChange', this.handleVisibleChange_);
},
/**
* TODO(dbeam): why is a private method being overridden?
* @override
* @private
*/
handleVisibleChange_: function() {
if (!this.visible)
return;
if (this.availableGalleriesList_)
this.availableGalleriesList_.redraw();
},
/**
* @param {Array} galleries List of structs describibing galleries.
* @private
*/
setAvailableMediaGalleries_: function(galleries) {
$('available-galleries-list').dataModel = new ArrayDataModel(galleries);
// TODO(estade): show this section by default.
$('media-galleries-section').hidden = false;
},
},
// Forward public APIs to private implementations.
[
'setAvailableMediaGalleries',
].forEach(function(name) {
MediaGalleriesManager[name] = function() {
var instance = MediaGalleriesManager.getInstance();
return instance[name + '_'].apply(instance, arguments);
};
});
// Export
return {
MediaGalleriesManager: MediaGalleriesManager
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var FocusManager = cr.ui.FocusManager;
var OptionsPage = options.OptionsPage;
function OptionsFocusManager() {
}
cr.addSingletonGetter(OptionsFocusManager);
OptionsFocusManager.prototype = {
__proto__: FocusManager.prototype,
/** @override */
getFocusParent: function() {
var topPage = OptionsPage.getTopmostVisiblePage().pageDiv;
// The default page and search page include a search field that is a
// sibling of the rest of the page instead of a child. Thus, use the
// parent node to allow the search field to receive focus.
if (topPage.parentNode.id == 'page-container')
return topPage.parentNode;
return topPage;
},
};
return {
OptionsFocusManager: OptionsFocusManager,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/////////////////////////////////////////////////////////////////////////////
// PasswordManager class:
/**
* Encapsulated handling of password and exceptions page.
* @constructor
*/
function PasswordManager() {
this.activeNavTab = null;
OptionsPage.call(this,
'passwords',
loadTimeData.getString('passwordsPageTabTitle'),
'password-manager');
}
cr.addSingletonGetter(PasswordManager);
PasswordManager.prototype = {
__proto__: OptionsPage.prototype,
/**
* The saved passwords list.
* @type {DeletableItemList}
* @private
*/
savedPasswordsList_: null,
/**
* The password exceptions list.
* @type {DeletableItemList}
* @private
*/
passwordExceptionsList_: null,
/**
* The timer id of the timer set on search query change events.
* @type {number}
* @private
*/
queryDelayTimerId_: 0,
/**
* The most recent search query, or null if the query is empty.
* @type {?string}
* @private
*/
lastQuery_: null,
/** @override */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
$('password-manager-confirm').onclick = function() {
OptionsPage.closeOverlay();
};
$('password-search-box').addEventListener('search',
this.handleSearchQueryChange_.bind(this));
this.createSavedPasswordsList_();
this.createPasswordExceptionsList_();
},
/** @override */
canShowPage: function() {
return !(cr.isChromeOS && UIAccountTweaks.loggedInAsGuest());
},
/** @override */
didShowPage: function() {
// Updating the password lists may cause a blocking platform dialog pop up
// (Mac, Linux), so we delay this operation until the page is shown.
chrome.send('updatePasswordLists');
$('password-search-box').focus();
},
/**
* Creates, decorates and initializes the saved passwords list.
* @private
*/
createSavedPasswordsList_: function() {
this.savedPasswordsList_ = $('saved-passwords-list');
options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_);
this.savedPasswordsList_.autoExpands = true;
},
/**
* Creates, decorates and initializes the password exceptions list.
* @private
*/
createPasswordExceptionsList_: function() {
this.passwordExceptionsList_ = $('password-exceptions-list');
options.passwordManager.PasswordExceptionsList.decorate(
this.passwordExceptionsList_);
this.passwordExceptionsList_.autoExpands = true;
},
/**
* Handles search query changes.
* @param {!Event} e The event object.
* @private
*/
handleSearchQueryChange_: function(e) {
if (this.queryDelayTimerId_)
window.clearTimeout(this.queryDelayTimerId_);
// Searching cookies uses a timeout of 500ms. We use a shorter timeout
// because there are probably fewer passwords and we want the UI to be
// snappier since users will expect that it's "less work."
this.queryDelayTimerId_ = window.setTimeout(
this.searchPasswords_.bind(this), 250);
},
/**
* Search passwords using text in |password-search-box|.
* @private
*/
searchPasswords_: function() {
this.queryDelayTimerId_ = 0;
var filter = $('password-search-box').value;
filter = (filter == '') ? null : filter;
if (this.lastQuery_ != filter) {
this.lastQuery_ = filter;
// Searching for passwords has the side effect of requerying the
// underlying password store. This is done intentionally, as on OS X and
// Linux they can change from outside and we won't be notified of it.
chrome.send('updatePasswordLists');
}
},
/**
* Updates the visibility of the list and empty list placeholder.
* @param {!List} list The list to toggle visilibility for.
*/
updateListVisibility_: function(list) {
var empty = list.dataModel.length == 0;
var listPlaceHolderID = list.id + '-empty-placeholder';
list.hidden = empty;
$(listPlaceHolderID).hidden = !empty;
},
/**
* Updates the data model for the saved passwords list with the values from
* |entries|.
* @param {Array} entries The list of saved password data.
*/
setSavedPasswordsList_: function(entries) {
if (this.lastQuery_) {
// Implement password searching here in javascript, rather than in C++.
// The number of saved passwords shouldn't be too big for us to handle.
var query = this.lastQuery_;
var filter = function(entry, index, list) {
// Search both URL and username.
if (entry[0].indexOf(query) >= 0 || entry[1].indexOf(query) >= 0) {
// Keep the original index so we can delete correctly. See also
// deleteItemAtIndex() in password_manager_list.js that uses this.
entry[3] = index;
return true;
}
return false;
};
entries = entries.filter(filter);
}
this.savedPasswordsList_.dataModel = new ArrayDataModel(entries);
this.updateListVisibility_(this.savedPasswordsList_);
},
/**
* Updates the data model for the password exceptions list with the values
* from |entries|.
* @param {Array} entries The list of password exception data.
*/
setPasswordExceptionsList_: function(entries) {
this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries);
this.updateListVisibility_(this.passwordExceptionsList_);
},
};
/**
* Removes a saved password.
* @param {number} rowIndex indicating the row to remove.
*/
PasswordManager.removeSavedPassword = function(rowIndex) {
chrome.send('removeSavedPassword', [String(rowIndex)]);
};
/**
* Removes a password exception.
* @param {number} rowIndex indicating the row to remove.
*/
PasswordManager.removePasswordException = function(rowIndex) {
chrome.send('removePasswordException', [String(rowIndex)]);
};
/**
* Removes all saved passwords.
*/
PasswordManager.removeAllPasswords = function() {
chrome.send('removeAllSavedPasswords');
};
/**
* Removes all password exceptions.
*/
PasswordManager.removeAllPasswordExceptions = function() {
chrome.send('removeAllPasswordExceptions');
};
PasswordManager.setSavedPasswordsList = function(entries) {
PasswordManager.getInstance().setSavedPasswordsList_(entries);
};
PasswordManager.setPasswordExceptionsList = function(entries) {
PasswordManager.getInstance().setPasswordExceptionsList_(entries);
};
// Export
return {
PasswordManager: PasswordManager
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.passwordManager', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var DeletableItemList = options.DeletableItemList;
/** @const */ var DeletableItem = options.DeletableItem;
/** @const */ var List = cr.ui.List;
/**
* Creates a new passwords list item.
* @param {Array} entry An array of the form [url, username, password]. When
* the list has been filtered, a fourth element [index] may be present.
* @constructor
* @extends {cr.ui.ListItem}
*/
function PasswordListItem(entry, showPasswords) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = PasswordListItem.prototype;
el.decorate(showPasswords);
return el;
}
PasswordListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @override */
decorate: function(showPasswords) {
DeletableItem.prototype.decorate.call(this);
// The URL of the site.
var urlLabel = this.ownerDocument.createElement('div');
urlLabel.classList.add('favicon-cell');
urlLabel.classList.add('weakrtl');
urlLabel.classList.add('url');
urlLabel.setAttribute('title', this.url);
urlLabel.textContent = this.url;
// The favicon URL is prefixed with "origin/", which essentially removes
// the URL path past the top-level domain and ensures that a scheme (e.g.,
// http) is being used. This ensures that the favicon returned is the
// default favicon for the domain and that the URL has a scheme if none
// is present in the password manager.
urlLabel.style.backgroundImage =
url('chrome://favicon/origin/' + this.url);
this.contentElement.appendChild(urlLabel);
// The stored username.
var usernameLabel = this.ownerDocument.createElement('div');
usernameLabel.className = 'name';
usernameLabel.textContent = this.username;
this.contentElement.appendChild(usernameLabel);
// The stored password.
var passwordInputDiv = this.ownerDocument.createElement('div');
passwordInputDiv.className = 'password';
// The password input field.
var passwordInput = this.ownerDocument.createElement('input');
passwordInput.type = 'password';
passwordInput.className = 'inactive-password';
passwordInput.readOnly = true;
passwordInput.value = showPasswords ? this.password : '********';
passwordInputDiv.appendChild(passwordInput);
// The show/hide button.
if (showPasswords) {
var button = this.ownerDocument.createElement('button');
button.hidden = true;
button.className = 'list-inline-button custom-appearance';
button.textContent = loadTimeData.getString('passwordShowButton');
button.addEventListener('click', this.onClick_, true);
passwordInputDiv.appendChild(button);
}
this.contentElement.appendChild(passwordInputDiv);
},
/** @override */
selectionChanged: function() {
var passwordInput = this.querySelector('input[type=password]');
var textInput = this.querySelector('input[type=text]');
var input = passwordInput || textInput;
var button = input.nextSibling;
// |button| doesn't exist when passwords can't be shown.
if (!button)
return;
if (this.selected) {
input.classList.remove('inactive-password');
button.hidden = false;
} else {
input.classList.add('inactive-password');
button.hidden = true;
}
},
/**
* On-click event handler. Swaps the type of the input field from password
* to text and back.
* @private
*/
onClick_: function(event) {
// The password is the input element previous to the button.
var button = event.currentTarget;
var passwordInput = button.previousSibling;
if (passwordInput.type == 'password') {
passwordInput.type = 'text';
button.textContent = loadTimeData.getString('passwordHideButton');
} else {
passwordInput.type = 'password';
button.textContent = loadTimeData.getString('passwordShowButton');
}
},
/**
* Get and set the URL for the entry.
* @type {string}
*/
get url() {
return this.dataItem[0];
},
set url(url) {
this.dataItem[0] = url;
},
/**
* Get and set the username for the entry.
* @type {string}
*/
get username() {
return this.dataItem[1];
},
set username(username) {
this.dataItem[1] = username;
},
/**
* Get and set the password for the entry.
* @type {string}
*/
get password() {
return this.dataItem[2];
},
set password(password) {
this.dataItem[2] = password;
},
};
/**
* Creates a new PasswordExceptions list item.
* @param {Array} entry A pair of the form [url, username].
* @constructor
* @extends {Deletable.ListItem}
*/
function PasswordExceptionsListItem(entry) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = PasswordExceptionsListItem.prototype;
el.decorate();
return el;
}
PasswordExceptionsListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* Call when an element is decorated as a list item.
*/
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The URL of the site.
var urlLabel = this.ownerDocument.createElement('div');
urlLabel.className = 'url';
urlLabel.classList.add('favicon-cell');
urlLabel.classList.add('weakrtl');
urlLabel.textContent = this.url;
// The favicon URL is prefixed with "origin/", which essentially removes
// the URL path past the top-level domain and ensures that a scheme (e.g.,
// http) is being used. This ensures that the favicon returned is the
// default favicon for the domain and that the URL has a scheme if none
// is present in the password manager.
urlLabel.style.backgroundImage =
url('chrome://favicon/origin/' + this.url);
this.contentElement.appendChild(urlLabel);
},
/**
* Get the url for the entry.
* @type {string}
*/
get url() {
return this.dataItem;
},
set url(url) {
this.dataItem = url;
},
};
/**
* Create a new passwords list.
* @constructor
* @extends {cr.ui.List}
*/
var PasswordsList = cr.ui.define('list');
PasswordsList.prototype = {
__proto__: DeletableItemList.prototype,
/**
* Whether passwords can be revealed or not.
* @type {boolean}
* @private
*/
showPasswords_: true,
/** @override */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
Preferences.getInstance().addEventListener(
'profile.password_manager_allow_show_passwords',
this.onPreferenceChanged_.bind(this));
},
/**
* Listener for changes on the preference.
* @param {Event} event The preference update event.
* @private
*/
onPreferenceChanged_: function(event) {
this.showPasswords_ = event.value.value;
this.redraw();
},
/** @override */
createItem: function(entry) {
return new PasswordListItem(entry, this.showPasswords_);
},
/** @override */
deleteItemAtIndex: function(index) {
var item = this.dataModel.item(index);
if (item && item.length > 3) {
// The fourth element, if present, is the original index to delete.
index = item[3];
}
PasswordManager.removeSavedPassword(index);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
};
/**
* Create a new passwords list.
* @constructor
* @extends {cr.ui.List}
*/
var PasswordExceptionsList = cr.ui.define('list');
PasswordExceptionsList.prototype = {
__proto__: DeletableItemList.prototype,
/** @override */
createItem: function(entry) {
return new PasswordExceptionsListItem(entry);
},
/** @override */
deleteItemAtIndex: function(index) {
PasswordManager.removePasswordException(index);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
};
return {
PasswordListItem: PasswordListItem,
PasswordExceptionsListItem: PasswordExceptionsListItem,
PasswordsList: PasswordsList,
PasswordExceptionsList: PasswordExceptionsList,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var ListItem = cr.ui.ListItem;
/** @const */ var Grid = cr.ui.Grid;
/** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
/**
* Creates a new profile icon grid item.
* @param {Object} iconURL The profile icon URL.
* @constructor
* @extends {cr.ui.GridItem}
*/
function ProfilesIconGridItem(iconURL) {
var el = cr.doc.createElement('span');
el.iconURL_ = iconURL;
ProfilesIconGridItem.decorate(el);
return el;
}
/**
* Decorates an element as a profile grid item.
* @param {!HTMLElement} el The element to decorate.
*/
ProfilesIconGridItem.decorate = function(el) {
el.__proto__ = ProfilesIconGridItem.prototype;
el.decorate();
};
ProfilesIconGridItem.prototype = {
__proto__: ListItem.prototype,
/** @override */
decorate: function() {
ListItem.prototype.decorate.call(this);
var imageEl = cr.doc.createElement('img');
imageEl.className = 'profile-icon';
imageEl.src = this.iconURL_;
this.appendChild(imageEl);
this.className = 'profile-icon-grid-item';
},
};
var ProfilesIconGrid = cr.ui.define('grid');
ProfilesIconGrid.prototype = {
__proto__: Grid.prototype,
/** @override */
decorate: function() {
Grid.prototype.decorate.call(this);
this.selectionModel = new ListSingleSelectionModel();
},
/** @override */
createItem: function(iconURL) {
return new ProfilesIconGridItem(iconURL);
},
};
return {
ProfilesIconGrid: ProfilesIconGrid
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/**
* Encapsulated handling of search engine management page.
* @constructor
*/
function SearchEngineManager() {
this.activeNavTab = null;
OptionsPage.call(this, 'searchEngines',
loadTimeData.getString('searchEngineManagerPageTabTitle'),
'search-engine-manager-page');
}
cr.addSingletonGetter(SearchEngineManager);
SearchEngineManager.prototype = {
__proto__: OptionsPage.prototype,
/**
* List for default search engine options.
* @private
*/
defaultsList_: null,
/**
* List for other search engine options.
* @private
*/
othersList_: null,
/**
* List for extension keywords.
* @private
extensionList_ : null,
/** inheritDoc */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.defaultsList_ = $('default-search-engine-list');
this.setUpList_(this.defaultsList_);
this.othersList_ = $('other-search-engine-list');
this.setUpList_(this.othersList_);
this.extensionList_ = $('extension-keyword-list');
this.setUpList_(this.extensionList_);
$('search-engine-manager-confirm').onclick = function() {
OptionsPage.closeOverlay();
};
},
/**
* Sets up the given list as a search engine list
* @param {List} list The list to set up.
* @private
*/
setUpList_: function(list) {
options.search_engines.SearchEngineList.decorate(list);
list.autoExpands = true;
},
/**
* Updates the search engine list with the given entries.
* @private
* @param {Array} defaultEngines List of possible default search engines.
* @param {Array} otherEngines List of other search engines.
* @param {Array} keywords List of keywords from extensions.
*/
updateSearchEngineList_: function(defaultEngines, otherEngines, keywords) {
this.defaultsList_.dataModel = new ArrayDataModel(defaultEngines);
otherEngines = otherEngines.map(function(x) {
return [x, x.name.toLocaleLowerCase()];
}).sort(function(a, b) {
return a[1].localeCompare(b[1]);
}).map(function(x) {
return x[0];
});
var othersModel = new ArrayDataModel(otherEngines);
// Add a "new engine" row.
othersModel.push({
'modelIndex': '-1',
'canBeEdited': true
});
this.othersList_.dataModel = othersModel;
if (keywords.length > 0) {
$('extension-keyword-div').hidden = false;
var extensionsModel = new ArrayDataModel(keywords);
this.extensionList_.dataModel = extensionsModel;
} else {
$('extension-keyword-div').hidden = true;
}
},
};
SearchEngineManager.updateSearchEngineList = function(defaultEngines,
otherEngines,
keywords) {
SearchEngineManager.getInstance().updateSearchEngineList_(defaultEngines,
otherEngines,
keywords);
};
SearchEngineManager.validityCheckCallback = function(validity, modelIndex) {
// Forward to both lists; the one without a matching modelIndex will ignore
// it.
SearchEngineManager.getInstance().defaultsList_.validationComplete(
validity, modelIndex);
SearchEngineManager.getInstance().othersList_.validationComplete(
validity, modelIndex);
};
// Export
return {
SearchEngineManager: SearchEngineManager
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.search_engines', function() {
/** @const */ var ControlledSettingIndicator =
options.ControlledSettingIndicator;
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var ListSelectionController = cr.ui.ListSelectionController;
/**
* Creates a new search engine list item.
* @param {Object} searchEnigne The search engine this represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function SearchEngineListItem(searchEngine) {
var el = cr.doc.createElement('div');
el.searchEngine_ = searchEngine;
SearchEngineListItem.decorate(el);
return el;
}
/**
* Decorates an element as a search engine list item.
* @param {!HTMLElement} el The element to decorate.
*/
SearchEngineListItem.decorate = function(el) {
el.__proto__ = SearchEngineListItem.prototype;
el.decorate();
};
SearchEngineListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Input field for editing the engine name.
* @type {HTMLElement}
* @private
*/
nameField_: null,
/**
* Input field for editing the engine keyword.
* @type {HTMLElement}
* @private
*/
keywordField_: null,
/**
* Input field for editing the engine url.
* @type {HTMLElement}
* @private
*/
urlField_: null,
/**
* Whether or not an input validation request is currently outstanding.
* @type {boolean}
* @private
*/
waitingForValidation_: false,
/**
* Whether or not the current set of input is known to be valid.
* @type {boolean}
* @private
*/
currentlyValid_: false,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
var engine = this.searchEngine_;
if (engine.modelIndex == '-1') {
this.isPlaceholder = true;
engine.name = '';
engine.keyword = '';
engine.url = '';
}
this.currentlyValid_ = !this.isPlaceholder;
if (engine.default)
this.classList.add('default');
this.deletable = engine.canBeRemoved;
// Construct the name column.
var nameColEl = this.ownerDocument.createElement('div');
nameColEl.className = 'name-column';
nameColEl.classList.add('weakrtl');
this.contentElement.appendChild(nameColEl);
// Add the favicon.
var faviconDivEl = this.ownerDocument.createElement('div');
faviconDivEl.className = 'favicon';
if (!this.isPlaceholder) {
faviconDivEl.style.backgroundImage =
url('chrome://favicon/iconurl@' + window.devicePixelRatio + 'x/' +
engine.iconURL);
}
nameColEl.appendChild(faviconDivEl);
var nameEl = this.createEditableTextCell(engine.displayName);
nameEl.classList.add('weakrtl');
nameColEl.appendChild(nameEl);
// Then the keyword column.
var keywordEl = this.createEditableTextCell(engine.keyword);
keywordEl.className = 'keyword-column';
keywordEl.classList.add('weakrtl');
this.contentElement.appendChild(keywordEl);
// And the URL column.
var urlEl = this.createEditableTextCell(engine.url);
var urlWithButtonEl = this.ownerDocument.createElement('div');
urlWithButtonEl.appendChild(urlEl);
urlWithButtonEl.className = 'url-column';
urlWithButtonEl.classList.add('weakrtl');
this.contentElement.appendChild(urlWithButtonEl);
// Add the Make Default button. Temporary until drag-and-drop re-ordering
// is implemented. When this is removed, remove the extra div above.
if (engine.canBeDefault) {
var makeDefaultButtonEl = this.ownerDocument.createElement('button');
makeDefaultButtonEl.className = 'custom-appearance list-inline-button';
makeDefaultButtonEl.textContent =
loadTimeData.getString('makeDefaultSearchEngineButton');
makeDefaultButtonEl.onclick = function(e) {
chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
};
// Don't select the row when clicking the button.
makeDefaultButtonEl.onmousedown = function(e) {
e.stopPropagation();
};
urlWithButtonEl.appendChild(makeDefaultButtonEl);
}
// Do final adjustment to the input fields.
this.nameField_ = nameEl.querySelector('input');
// The editable field uses the raw name, not the display name.
this.nameField_.value = engine.name;
this.keywordField_ = keywordEl.querySelector('input');
this.urlField_ = urlEl.querySelector('input');
if (engine.urlLocked)
this.urlField_.disabled = true;
if (this.isPlaceholder) {
this.nameField_.placeholder =
loadTimeData.getString('searchEngineTableNamePlaceholder');
this.keywordField_.placeholder =
loadTimeData.getString('searchEngineTableKeywordPlaceholder');
this.urlField_.placeholder =
loadTimeData.getString('searchEngineTableURLPlaceholder');
}
var fields = [this.nameField_, this.keywordField_, this.urlField_];
for (var i = 0; i < fields.length; i++) {
fields[i].oninput = this.startFieldValidation_.bind(this);
}
// Listen for edit events.
if (engine.canBeEdited) {
this.addEventListener('edit', this.onEditStarted_.bind(this));
this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
} else {
this.editable = false;
this.querySelector('.row-delete-button').hidden = true;
var indicator = ControlledSettingIndicator();
indicator.setAttribute('setting', 'search-engine');
// Create a synthetic pref change event decorated as
// CoreOptionsHandler::CreateValueForPref() does.
var event = new cr.Event(this.contentType);
event.value = { controlledBy: 'policy' };
indicator.handlePrefChange(event);
this.appendChild(indicator);
}
},
/** @override */
get currentInputIsValid() {
return !this.waitingForValidation_ && this.currentlyValid_;
},
/** @override */
get hasBeenEdited() {
var engine = this.searchEngine_;
return this.nameField_.value != engine.name ||
this.keywordField_.value != engine.keyword ||
this.urlField_.value != engine.url;
},
/**
* Called when entering edit mode; starts an edit session in the model.
* @param {Event} e The edit event.
* @private
*/
onEditStarted_: function(e) {
var editIndex = this.searchEngine_.modelIndex;
chrome.send('editSearchEngine', [String(editIndex)]);
this.startFieldValidation_();
},
/**
* Called when committing an edit; updates the model.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
},
/**
* Called when cancelling an edit; informs the model and resets the control
* states.
* @param {Event} e The cancel event.
* @private
*/
onEditCancelled_: function() {
chrome.send('searchEngineEditCancelled');
// The name field has been automatically set to match the display name,
// but it should use the raw name instead.
this.nameField_.value = this.searchEngine_.name;
this.currentlyValid_ = !this.isPlaceholder;
},
/**
* Returns the input field values as an array suitable for passing to
* chrome.send. The order of the array is important.
* @private
* @return {array} The current input field values.
*/
getInputFieldValues_: function() {
return [this.nameField_.value,
this.keywordField_.value,
this.urlField_.value];
},
/**
* Begins the process of asynchronously validing the input fields.
* @private
*/
startFieldValidation_: function() {
this.waitingForValidation_ = true;
var args = this.getInputFieldValues_();
args.push(this.searchEngine_.modelIndex);
chrome.send('checkSearchEngineInfoValidity', args);
},
/**
* Callback for the completion of an input validition check.
* @param {Object} validity A dictionary of validitation results.
*/
validationComplete: function(validity) {
this.waitingForValidation_ = false;
// TODO(stuartmorgan): Implement the full validation UI with
// checkmark/exclamation mark icons and tooltips showing the errors.
if (validity.name) {
this.nameField_.setCustomValidity('');
} else {
this.nameField_.setCustomValidity(
loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
}
if (validity.keyword) {
this.keywordField_.setCustomValidity('');
} else {
this.keywordField_.setCustomValidity(
loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
}
if (validity.url) {
this.urlField_.setCustomValidity('');
} else {
this.urlField_.setCustomValidity(
loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
}
this.currentlyValid_ = validity.name && validity.keyword && validity.url;
},
};
var SearchEngineList = cr.ui.define('list');
SearchEngineList.prototype = {
__proto__: InlineEditableItemList.prototype,
/** @override */
createItem: function(searchEngine) {
return new SearchEngineListItem(searchEngine);
},
/** @override */
deleteItemAtIndex: function(index) {
var modelIndex = this.dataModel.item(index).modelIndex;
chrome.send('removeSearchEngine', [String(modelIndex)]);
},
/**
* Passes the results of an input validation check to the requesting row
* if it's still being edited.
* @param {number} modelIndex The model index of the item that was checked.
* @param {Object} validity A dictionary of validitation results.
*/
validationComplete: function(validity, modelIndex) {
// If it's not still being edited, it no longer matters.
var currentSelection = this.selectedItem;
if (!currentSelection)
return;
var listItem = this.getListItem(currentSelection);
if (listItem.editing && currentSelection.modelIndex == modelIndex)
listItem.validationComplete(validity);
},
};
// Export
return {
SearchEngineList: SearchEngineList
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
/**
* Encapsulated handling of a search bubble.
* @constructor
*/
function SearchBubble(text) {
var el = cr.doc.createElement('div');
SearchBubble.decorate(el);
el.content = text;
return el;
}
SearchBubble.decorate = function(el) {
el.__proto__ = SearchBubble.prototype;
el.decorate();
};
SearchBubble.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.className = 'search-bubble';
this.innards_ = cr.doc.createElement('div');
this.innards_.className = 'search-bubble-innards';
this.appendChild(this.innards_);
// We create a timer to periodically update the position of the bubbles.
// While this isn't all that desirable, it's the only sure-fire way of
// making sure the bubbles stay in the correct location as sections
// may dynamically change size at any time.
this.intervalId = setInterval(this.updatePosition.bind(this), 250);
},
/**
* Sets the text message in the bubble.
* @param {string} text The text the bubble will show.
*/
set content(text) {
this.innards_.textContent = text;
},
/**
* Attach the bubble to the element.
*/
attachTo: function(element) {
var parent = element.parentElement;
if (!parent)
return;
if (parent.tagName == 'TD') {
// To make absolute positioning work inside a table cell we need
// to wrap the bubble div into another div with position:relative.
// This only works properly if the element is the first child of the
// table cell which is true for all options pages.
this.wrapper = cr.doc.createElement('div');
this.wrapper.className = 'search-bubble-wrapper';
this.wrapper.appendChild(this);
parent.insertBefore(this.wrapper, element);
} else {
parent.insertBefore(this, element);
}
},
/**
* Clear the interval timer and remove the element from the page.
*/
dispose: function() {
clearInterval(this.intervalId);
var child = this.wrapper || this;
var parent = child.parentNode;
if (parent)
parent.removeChild(child);
},
/**
* Update the position of the bubble. Called at creation time and then
* periodically while the bubble remains visible.
*/
updatePosition: function() {
// This bubble is 'owned' by the next sibling.
var owner = (this.wrapper || this).nextSibling;
// If there isn't an offset parent, we have nothing to do.
if (!owner.offsetParent)
return;
// Position the bubble below the location of the owner.
var left = owner.offsetLeft + owner.offsetWidth / 2 -
this.offsetWidth / 2;
var top = owner.offsetTop + owner.offsetHeight;
// Update the position in the CSS. Cache the last values for
// best performance.
if (left != this.lastLeft) {
this.style.left = left + 'px';
this.lastLeft = left;
}
if (top != this.lastTop) {
this.style.top = top + 'px';
this.lastTop = top;
}
},
};
/**
* Encapsulated handling of the search page.
* @constructor
*/
function SearchPage() {
OptionsPage.call(this, 'search',
loadTimeData.getString('searchPageTabTitle'),
'searchPage');
}
cr.addSingletonGetter(SearchPage);
SearchPage.prototype = {
// Inherit SearchPage from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* A boolean to prevent recursion. Used by setSearchText_().
* @type {Boolean}
* @private
*/
insideSetSearchText_: false,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
this.searchField = $('search-field');
// Handle search events. (No need to throttle, WebKit's search field
// will do that automatically.)
this.searchField.onsearch = function(e) {
this.setSearchText_(e.currentTarget.value);
}.bind(this);
// Install handler for key presses.
document.addEventListener('keydown',
this.keyDownEventHandler_.bind(this));
},
/** @override */
get sticky() {
return true;
},
/**
* Called after this page has shown.
*/
didShowPage: function() {
// This method is called by the Options page after all pages have
// had their visibilty attribute set. At this point we can perform the
// search specific DOM manipulation.
this.setSearchActive_(true);
},
/**
* Called before this page will be hidden.
*/
willHidePage: function() {
// This method is called by the Options page before all pages have
// their visibilty attribute set. Before that happens, we need to
// undo the search specific DOM manipulation that was performed in
// didShowPage.
this.setSearchActive_(false);
},
/**
* Update the UI to reflect whether we are in a search state.
* @param {boolean} active True if we are on the search page.
* @private
*/
setSearchActive_: function(active) {
// It's fine to exit if search wasn't active and we're not going to
// activate it now.
if (!this.searchActive_ && !active)
return;
this.searchActive_ = active;
if (active) {
var hash = location.hash;
if (hash) {
this.searchField.value =
decodeURIComponent(hash.slice(1).replace(/\+/g, ' '));
} else if (!this.searchField.value) {
// This should only happen if the user goes directly to
// chrome://settings-frame/search
OptionsPage.showDefaultPage();
return;
}
// Move 'advanced' sections into the main settings page to allow
// searching.
if (!this.advancedSections_) {
this.advancedSections_ =
$('advanced-settings-container').querySelectorAll('section');
for (var i = 0, section; section = this.advancedSections_[i]; i++)
$('settings').appendChild(section);
}
}
var pagesToSearch = this.getSearchablePages_();
for (var key in pagesToSearch) {
var page = pagesToSearch[key];
if (!active)
page.visible = false;
// Update the visible state of all top-level elements that are not
// sections (ie titles, button strips). We do this before changing
// the page visibility to avoid excessive re-draw.
for (var i = 0, childDiv; childDiv = page.pageDiv.children[i]; i++) {
if (active) {
if (childDiv.tagName != 'SECTION')
childDiv.classList.add('search-hidden');
} else {
childDiv.classList.remove('search-hidden');
}
}
if (active) {
// When search is active, remove the 'hidden' tag. This tag may have
// been added by the OptionsPage.
page.pageDiv.hidden = false;
}
}
if (active) {
this.setSearchText_(this.searchField.value);
this.searchField.focus();
} else {
// After hiding all page content, remove any search results.
this.unhighlightMatches_();
this.removeSearchBubbles_();
// Move 'advanced' sections back into their original container.
if (this.advancedSections_) {
for (var i = 0, section; section = this.advancedSections_[i]; i++)
$('advanced-settings-container').appendChild(section);
this.advancedSections_ = null;
}
}
},
/**
* Set the current search criteria.
* @param {string} text Search text.
* @private
*/
setSearchText_: function(text) {
// Prevent recursive execution of this method.
if (this.insideSetSearchText_) return;
this.insideSetSearchText_ = true;
// Cleanup the search query string.
text = SearchPage.canonicalizeQuery(text);
// Set the hash on the current page, and the enclosing uber page
var hash = text ? '#' + encodeURIComponent(text) : '';
var path = text ? this.name : '';
window.location.hash = hash;
uber.invokeMethodOnParent('setPath', {path: path + hash});
// Toggle the search page if necessary.
if (text) {
if (!this.searchActive_)
OptionsPage.showPageByName(this.name, false);
} else {
if (this.searchActive_)
OptionsPage.showPageByName(OptionsPage.getDefaultPage().name, false);
this.insideSetSearchText_ = false;
return;
}
var foundMatches = false;
// Remove any prior search results.
this.unhighlightMatches_();
this.removeSearchBubbles_();
var pagesToSearch = this.getSearchablePages_();
for (var key in pagesToSearch) {
var page = pagesToSearch[key];
var elements = page.pageDiv.querySelectorAll('section');
for (var i = 0, node; node = elements[i]; i++) {
node.classList.add('search-hidden');
}
}
var bubbleControls = [];
// Generate search text by applying lowercase and escaping any characters
// that would be problematic for regular expressions.
var searchText =
text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
// Generate a regular expression for hilighting search terms.
var regExp = new RegExp('(' + searchText + ')', 'ig');
if (searchText.length) {
// Search all top-level sections for anchored string matches.
for (var key in pagesToSearch) {
var page = pagesToSearch[key];
var elements =
page.pageDiv.querySelectorAll('section');
for (var i = 0, node; node = elements[i]; i++) {
if (this.highlightMatches_(regExp, node)) {
node.classList.remove('search-hidden');
if (!node.hidden)
foundMatches = true;
}
}
}
// Search all sub-pages, generating an array of top-level sections that
// we need to make visible.
var subPagesToSearch = this.getSearchableSubPages_();
var control, node;
for (var key in subPagesToSearch) {
var page = subPagesToSearch[key];
if (this.highlightMatches_(regExp, page.pageDiv)) {
this.revealAssociatedSections_(page);
bubbleControls =
bubbleControls.concat(this.getAssociatedControls_(page));
foundMatches = true;
}
}
}
// Configure elements on the search results page based on search results.
$('searchPageNoMatches').hidden = foundMatches;
// Create search balloons for sub-page results.
length = bubbleControls.length;
for (var i = 0; i < length; i++)
this.createSearchBubble_(bubbleControls[i], text);
// Cleanup the recursion-prevention variable.
this.insideSetSearchText_ = false;
},
/**
* Reveal the associated section for |subpage|, as well as the one for its
* |parentPage|, and its |parentPage|'s |parentPage|, etc.
* @private
*/
revealAssociatedSections_: function(subpage) {
for (var page = subpage; page; page = page.parentPage) {
var section = page.associatedSection;
if (section)
section.classList.remove('search-hidden');
}
},
/**
* @return {!Array.<HTMLElement>} all the associated controls for |subpage|,
* including |subpage.associatedControls| as well as any controls on parent
* pages that are indirectly necessary to get to the subpage.
* @private
*/
getAssociatedControls_: function(subpage) {
var controls = [];
for (var page = subpage; page; page = page.parentPage) {
if (page.associatedControls)
controls = controls.concat(page.associatedControls);
}
return controls;
},
/**
* Wraps matches in spans.
* @param {RegExp} regExp The search query (in regexp form).
* @param {Element} element An HTML container element to recursively search
* within.
* @return {boolean} true if the element was changed.
* @private
*/
highlightMatches_: function(regExp, element) {
var found = false;
var div, child, tmp;
// Walk the tree, searching each TEXT node.
var walker = document.createTreeWalker(element,
NodeFilter.SHOW_TEXT,
null,
false);
var node = walker.nextNode();
while (node) {
var textContent = node.nodeValue;
// Perform a search and replace on the text node value.
var split = textContent.split(regExp);
if (split.length > 1) {
found = true;
var nextNode = walker.nextNode();
var parentNode = node.parentNode;
// Use existing node as placeholder to determine where to insert the
// replacement content.
for (var i = 0; i < split.length; ++i) {
if (i % 2 == 0) {
parentNode.insertBefore(document.createTextNode(split[i]), node);
} else {
var span = document.createElement('span');
span.className = 'search-highlighted';
span.textContent = split[i];
parentNode.insertBefore(span, node);
}
}
// Remove old node.
parentNode.removeChild(node);
node = nextNode;
} else {
node = walker.nextNode();
}
}
return found;
},
/**
* Removes all search highlight tags from the document.
* @private
*/
unhighlightMatches_: function() {
// Find all search highlight elements.
var elements = document.querySelectorAll('.search-highlighted');
// For each element, remove the highlighting.
var parent, i;
for (var i = 0, node; node = elements[i]; i++) {
parent = node.parentNode;
// Replace the highlight element with the first child (the text node).
parent.replaceChild(node.firstChild, node);
// Normalize the parent so that multiple text nodes will be combined.
parent.normalize();
}
},
/**
* Creates a search result bubble attached to an element.
* @param {Element} element An HTML element, usually a button.
* @param {string} text A string to show in the bubble.
* @private
*/
createSearchBubble_: function(element, text) {
// avoid appending multiple bubbles to a button.
var sibling = element.previousElementSibling;
if (sibling && (sibling.classList.contains('search-bubble') ||
sibling.classList.contains('search-bubble-wrapper')))
return;
var parent = element.parentElement;
if (parent) {
var bubble = new SearchBubble(text);
bubble.attachTo(element);
bubble.updatePosition();
}
},
/**
* Removes all search match bubbles.
* @private
*/
removeSearchBubbles_: function() {
var elements = document.querySelectorAll('.search-bubble');
var length = elements.length;
for (var i = 0; i < length; i++)
elements[i].dispose();
},
/**
* Builds a list of top-level pages to search. Omits the search page and
* all sub-pages.
* @return {Array} An array of pages to search.
* @private
*/
getSearchablePages_: function() {
var name, page, pages = [];
for (name in OptionsPage.registeredPages) {
if (name != this.name) {
page = OptionsPage.registeredPages[name];
if (!page.parentPage)
pages.push(page);
}
}
return pages;
},
/**
* Builds a list of sub-pages (and overlay pages) to search. Ignore pages
* that have no associated controls.
* @return {Array} An array of pages to search.
* @private
*/
getSearchableSubPages_: function() {
var name, pageInfo, page, pages = [];
for (name in OptionsPage.registeredPages) {
page = OptionsPage.registeredPages[name];
if (page.parentPage && page.associatedSection)
pages.push(page);
}
for (name in OptionsPage.registeredOverlayPages) {
page = OptionsPage.registeredOverlayPages[name];
if (page.associatedSection && page.pageDiv != undefined)
pages.push(page);
}
return pages;
},
/**
* A function to handle key press events.
* @return {Event} a keydown event.
* @private
*/
keyDownEventHandler_: function(event) {
/** @const */ var ESCAPE_KEY_CODE = 27;
/** @const */ var FORWARD_SLASH_KEY_CODE = 191;
switch (event.keyCode) {
case ESCAPE_KEY_CODE:
if (event.target == this.searchField) {
this.setSearchText_('');
this.searchField.blur();
event.stopPropagation();
event.preventDefault();
}
break;
case FORWARD_SLASH_KEY_CODE:
if (!/INPUT|SELECT|BUTTON|TEXTAREA/.test(event.target.tagName) &&
!event.ctrlKey && !event.altKey) {
this.searchField.focus();
event.stopPropagation();
event.preventDefault();
}
break;
}
},
};
/**
* Standardizes a user-entered text query by removing extra whitespace.
* @param {string} The user-entered text.
* @return {string} The trimmed query.
*/
SearchPage.canonicalizeQuery = function(text) {
// Trim beginning and ending whitespace.
return text.replace(/^\s+|\s+$/g, '');
};
// Export
return {
SearchPage: SearchPage
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var OptionsPage = options.OptionsPage;
/** @const */ var SettingsDialog = options.SettingsDialog;
/**
* StartupOverlay class
* Encapsulated handling of the 'Set Startup pages' overlay page.
* @constructor
* @class
*/
function StartupOverlay() {
SettingsDialog.call(this, 'startup',
loadTimeData.getString('startupPagesOverlayTabTitle'),
'startup-overlay',
$('startup-overlay-confirm'),
$('startup-overlay-cancel'));
};
cr.addSingletonGetter(StartupOverlay);
StartupOverlay.prototype = {
__proto__: SettingsDialog.prototype,
/**
* An autocomplete list that can be attached to a text field during editing.
* @type {HTMLElement}
* @private
*/
autocompleteList_: null,
startup_pages_pref_: {
'name': 'session.urls_to_restore_on_startup',
'disabled': false
},
/**
* Initialize the page.
*/
initializePage: function() {
SettingsDialog.prototype.initializePage.call(this);
var self = this;
var startupPagesList = $('startupPagesList');
options.browser_options.StartupPageList.decorate(startupPagesList);
startupPagesList.autoExpands = true;
$('startupUseCurrentButton').onclick = function(event) {
chrome.send('setStartupPagesToCurrentPages');
};
Preferences.getInstance().addEventListener(
this.startup_pages_pref_.name,
this.handleStartupPageListChange_.bind(this));
var suggestionList = new cr.ui.AutocompleteList();
suggestionList.autoExpands = true;
suggestionList.suggestionUpdateRequestCallback =
this.requestAutocompleteSuggestions_.bind(this);
$('startup-overlay').appendChild(suggestionList);
this.autocompleteList_ = suggestionList;
startupPagesList.autocompleteList = suggestionList;
},
/** @override */
handleConfirm: function() {
SettingsDialog.prototype.handleConfirm.call(this);
chrome.send('commitStartupPrefChanges');
},
/** @override */
handleCancel: function() {
SettingsDialog.prototype.handleCancel.call(this);
chrome.send('cancelStartupPrefChanges');
},
/**
* Sets the enabled state of the custom startup page list
* @param {boolean} disable True to disable, false to enable
*/
setControlsDisabled: function(disable) {
var startupPagesList = $('startupPagesList');
startupPagesList.disabled = disable;
startupPagesList.setAttribute('tabindex', disable ? -1 : 0);
// Explicitly set disabled state for input text elements.
var inputs = startupPagesList.querySelectorAll("input[type='text']");
for (var i = 0; i < inputs.length; i++)
inputs[i].disabled = disable;
$('startupUseCurrentButton').disabled = disable;
},
/**
* Enables or disables the the custom startup page list controls
* based on the whether the 'pages to restore on startup' pref is enabled.
*/
updateControlStates: function() {
this.setControlsDisabled(
this.startup_pages_pref_.disabled);
},
/**
* Handles change events of the preference
* 'session.urls_to_restore_on_startup'.
* @param {event} preference changed event.
* @private
*/
handleStartupPageListChange_: function(event) {
this.startup_pages_pref_.disabled = event.value.disabled;
this.updateControlStates();
},
/**
* Updates the startup pages list with the given entries.
* @param {Array} pages List of startup pages.
* @private
*/
updateStartupPages_: function(pages) {
var model = new ArrayDataModel(pages);
// Add a "new page" row.
model.push({
'modelIndex': '-1'
});
$('startupPagesList').dataModel = model;
},
/**
* Sends an asynchronous request for new autocompletion suggestions for the
* the given query. When new suggestions are available, the C++ handler will
* call updateAutocompleteSuggestions_.
* @param {string} query List of autocomplete suggestions.
* @private
*/
requestAutocompleteSuggestions_: function(query) {
chrome.send('requestAutocompleteSuggestionsForStartupPages', [query]);
},
/**
* Updates the autocomplete suggestion list with the given entries.
* @param {Array} pages List of autocomplete suggestions.
* @private
*/
updateAutocompleteSuggestions_: function(suggestions) {
var list = this.autocompleteList_;
// If the trigger for this update was a value being selected from the
// current list, do nothing.
if (list.targetInput && list.selectedItem &&
list.selectedItem.url == list.targetInput.value) {
return;
}
list.suggestions = suggestions;
},
};
// Forward public APIs to private implementations.
[
'updateStartupPages',
'updateAutocompleteSuggestions',
].forEach(function(name) {
StartupOverlay[name] = function() {
var instance = StartupOverlay.getInstance();
return instance[name + '_'].apply(instance, arguments);
};
});
// Export
return {
StartupOverlay: StartupOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/** @const */ var OptionsPage = options.OptionsPage;
// Variable to track if a captcha challenge was issued. If this gets set to
// true, it stays that way until we are told about successful login from
// the browser. This means subsequent errors (like invalid password) are
// rendered in the captcha state, which is basically identical except we
// don't show the top error blurb 'Error Signing in' or the 'Create
// account' link.
var captchaChallengeActive_ = false;
// When true, the password value may be empty when submitting auth info.
// This is true when requesting an access code or when requesting an OTP or
// captcha with the oauth sign in flow.
var allowEmptyPassword_ = false;
// True if the synced account uses a custom passphrase.
var usePassphrase_ = false;
// True if the synced account uses 'encrypt everything'.
var useEncryptEverything_ = false;
// True if the support for keystore encryption is enabled. Controls whether
// the new unified encryption UI is displayed instead of the old encryption
// ui (where passphrase and encrypted types could be set independently of
// each other).
var keystoreEncryptionEnabled_ = false;
// The last email address that this profile was connected to. If the profile
// was never connected this is an empty string. Otherwise it is a normalized
// email address.
var lastEmailAddress_ = '';
/**
* SyncSetupOverlay class
* Encapsulated handling of the 'Sync Setup' overlay page.
* @class
*/
function SyncSetupOverlay() {
OptionsPage.call(this, 'syncSetup',
loadTimeData.getString('syncSetupOverlayTabTitle'),
'sync-setup-overlay');
}
cr.addSingletonGetter(SyncSetupOverlay);
SyncSetupOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('gaia-login-form').onsubmit = function() {
self.sendCredentialsAndClose_();
return false;
};
$('google-option').onchange = $('explicit-option').onchange = function() {
self.onPassphraseRadioChanged_();
};
$('basic-encryption-option').onchange =
$('full-encryption-option').onchange = function() {
self.onEncryptionRadioChanged_();
}
$('choose-datatypes-cancel').onclick =
$('sync-setup-cancel').onclick =
$('confirm-everything-cancel').onclick =
$('stop-syncing-cancel').onclick =
$('sync-spinner-cancel').onclick = function() {
self.closeOverlay_();
};
$('confirm-everything-ok').onclick = function() {
self.sendConfiguration_();
};
$('timeout-ok').onclick = function() {
chrome.send('CloseTimeout');
self.closeOverlay_();
};
$('stop-syncing-ok').onclick = function() {
chrome.send('SyncSetupStopSyncing');
self.closeOverlay_();
};
$('different-email').innerHTML = loadTimeData.getString('differentEmail');
},
showOverlay_: function() {
OptionsPage.navigateToPage('syncSetup');
},
closeOverlay_: function() {
OptionsPage.closeOverlay();
},
/** @override */
didShowPage: function() {
var forceLogin = document.location.hash == '#forceLogin';
var result = JSON.stringify({'forceLogin': forceLogin});
chrome.send('SyncSetupAttachHandler', [result]);
},
/** @override */
didClosePage: function() {
chrome.send('SyncSetupDidClosePage');
},
getEncryptionRadioCheckedValue_: function() {
var f = $('choose-data-types-form');
for (var i = 0; i < f.encrypt.length; ++i) {
if (f.encrypt[i].checked)
return f.encrypt[i].value;
}
return undefined;
},
getPassphraseRadioCheckedValue_: function() {
var f = $('choose-data-types-form');
for (var i = 0; i < f.option.length; ++i) {
if (f.option[i].checked) {
return f.option[i].value;
}
}
return undefined;
},
disableEncryptionRadioGroup_: function() {
var f = $('choose-data-types-form');
for (var i = 0; i < f.encrypt.length; ++i)
f.encrypt[i].disabled = true;
},
onPassphraseRadioChanged_: function() {
var visible = this.getPassphraseRadioCheckedValue_() == 'explicit';
$('sync-custom-passphrase').hidden = !visible;
},
onEncryptionRadioChanged_: function() {
var visible = $('full-encryption-option').checked;
$('sync-custom-passphrase').hidden = !visible;
},
checkAllDataTypeCheckboxes_: function() {
// Only check the visible ones (since there's no way to uncheck
// the invisible ones).
var checkboxes = $('choose-data-types-body').querySelectorAll(
'.sync-type-checkbox:not([hidden]) input');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = true;
}
},
setDataTypeCheckboxesEnabled_: function(enabled) {
var checkboxes = $('choose-data-types-body').querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].disabled = !enabled;
}
},
setCheckboxesToKeepEverythingSynced_: function(value) {
this.setDataTypeCheckboxesEnabled_(!value);
if (value)
this.checkAllDataTypeCheckboxes_();
},
// Returns true if none of the visible checkboxes are checked.
noDataTypesChecked_: function() {
var query = '.sync-type-checkbox:not([hidden]) input:checked';
var checkboxes = $('choose-data-types-body').querySelectorAll(query);
return checkboxes.length == 0;
},
checkPassphraseMatch_: function() {
var emptyError = $('empty-error');
var mismatchError = $('mismatch-error');
emptyError.hidden = true;
mismatchError.hidden = true;
var f = $('choose-data-types-form');
if ((this.getPassphraseRadioCheckedValue_() != 'explicit' ||
$('google-option').disabled) &&
(!$('full-encryption-option').checked ||
$('basic-encryption-option').disabled)) {
return true;
}
var customPassphrase = $('custom-passphrase');
if (customPassphrase.value.length == 0) {
emptyError.hidden = false;
return false;
}
var confirmPassphrase = $('confirm-passphrase');
if (confirmPassphrase.value != customPassphrase.value) {
mismatchError.hidden = false;
return false;
}
return true;
},
sendConfiguration_: function() {
// Trying to submit, so hide previous errors.
$('error-text').hidden = true;
var syncAll = $('sync-select-datatypes').selectedIndex == 0;
if (!syncAll && this.noDataTypesChecked_()) {
$('error-text').hidden = false;
return;
}
var encryptAllData = this.getEncryptionRadioCheckedValue_() == 'all';
if (!encryptAllData &&
$('full-encryption-option').checked &&
this.keystoreEncryptionEnabled_) {
encryptAllData = true;
}
var usePassphrase;
var customPassphrase;
var googlePassphrase = false;
if (!$('sync-existing-passphrase-container').hidden) {
// If we were prompted for an existing passphrase, use it.
customPassphrase = $('choose-data-types-form').passphrase.value;
usePassphrase = true;
// If we were displaying the 'enter your old google password' prompt,
// then that means this is the user's google password.
googlePassphrase = !$('google-passphrase-needed-body').hidden;
// We allow an empty passphrase, in case the user has disabled
// all their encrypted datatypes. In that case, the PSS will accept
// the passphrase and finish configuration. If the user has enabled
// encrypted datatypes, the PSS will prompt again specifying that the
// passphrase failed.
} else if ((!$('google-option').disabled &&
this.getPassphraseRadioCheckedValue_() == 'explicit') ||
(!$('basic-encryption-option').disabled &&
$('full-encryption-option').checked)) {
// The user is setting a custom passphrase for the first time.
if (!this.checkPassphraseMatch_())
return;
customPassphrase = $('custom-passphrase').value;
usePassphrase = true;
} else {
// The user is not setting a custom passphrase.
usePassphrase = false;
}
// Don't allow the user to tweak the settings once we send the
// configuration to the backend.
this.setInputElementsDisabledState_(true);
this.animateDisableLink_($('use-default-link'), true, null);
// These values need to be kept in sync with where they are read in
// SyncSetupFlow::GetDataTypeChoiceData().
var result = JSON.stringify({
'syncAllDataTypes': syncAll,
'bookmarksSynced': syncAll || $('bookmarks-checkbox').checked,
'preferencesSynced': syncAll || $('preferences-checkbox').checked,
'themesSynced': syncAll || $('themes-checkbox').checked,
'passwordsSynced': syncAll || $('passwords-checkbox').checked,
'autofillSynced': syncAll || $('autofill-checkbox').checked,
'extensionsSynced': syncAll || $('extensions-checkbox').checked,
'typedUrlsSynced': syncAll || $('typed-urls-checkbox').checked,
'appsSynced': syncAll || $('apps-checkbox').checked,
'sessionsSynced': syncAll || $('sessions-checkbox').checked,
'encryptAllData': encryptAllData,
'usePassphrase': usePassphrase,
'isGooglePassphrase': googlePassphrase,
'passphrase': customPassphrase
});
chrome.send('SyncSetupConfigure', [result]);
},
/**
* Sets the disabled property of all input elements within the 'Customize
* Sync Preferences' screen. This is used to prohibit the user from changing
* the inputs after confirming the customized sync preferences, or resetting
* the state when re-showing the dialog.
* @param {boolean} disabled True if controls should be set to disabled.
* @private
*/
setInputElementsDisabledState_: function(disabled) {
var configureElements =
$('customize-sync-preferences').querySelectorAll('input');
for (var i = 0; i < configureElements.length; i++)
configureElements[i].disabled = disabled;
$('sync-select-datatypes').disabled = disabled;
var self = this;
this.animateDisableLink_($('customize-link'), disabled, function() {
self.showCustomizePage_(null, true);
});
},
/**
* Animate a link being enabled/disabled. The link is hidden by animating
* its opacity, but to ensure the user doesn't click it during that time,
* its onclick handler is changed to null as well.
* @param {HTMLElement} elt The anchor element to enable/disable.
* @param {boolean} disabled True if the link should be disabled.
* @param {function} enabledFunction The onclick handler when the link is
* enabled.
* @private
*/
animateDisableLink_: function(elt, disabled, enabledFunction) {
if (disabled) {
elt.classList.add('transparent');
elt.onclick = null;
elt.addEventListener('webkitTransitionEnd', function f(e) {
if (e.propertyName != 'opacity')
return;
elt.removeEventListener('webkitTransitionEnd', f);
elt.classList.remove('transparent');
elt.hidden = true;
});
} else {
elt.hidden = false;
elt.onclick = enabledFunction;
}
},
/**
* Shows or hides the Sync data type checkboxes in the advanced
* configuration screen.
* @param {Object} args The configuration data used to show/hide UI.
* @private
*/
setChooseDataTypesCheckboxes_: function(args) {
var datatypeSelect = $('sync-select-datatypes');
datatypeSelect.selectedIndex = args.syncAllDataTypes ? 0 : 1;
$('bookmarks-checkbox').checked = args.bookmarksSynced;
$('preferences-checkbox').checked = args.preferencesSynced;
$('themes-checkbox').checked = args.themesSynced;
if (args.passwordsRegistered) {
$('passwords-checkbox').checked = args.passwordsSynced;
$('passwords-item').hidden = false;
} else {
$('passwords-item').hidden = true;
}
if (args.autofillRegistered) {
$('autofill-checkbox').checked = args.autofillSynced;
$('autofill-item').hidden = false;
} else {
$('autofill-item').hidden = true;
}
if (args.extensionsRegistered) {
$('extensions-checkbox').checked = args.extensionsSynced;
$('extensions-item').hidden = false;
} else {
$('extensions-item').hidden = true;
}
if (args.typedUrlsRegistered) {
$('typed-urls-checkbox').checked = args.typedUrlsSynced;
$('omnibox-item').hidden = false;
} else {
$('omnibox-item').hidden = true;
}
if (args.appsRegistered) {
$('apps-checkbox').checked = args.appsSynced;
$('apps-item').hidden = false;
} else {
$('apps-item').hidden = true;
}
if (args.sessionsRegistered) {
$('sessions-checkbox').checked = args.sessionsSynced;
$('sessions-item').hidden = false;
} else {
$('sessions-item').hidden = true;
}
this.setCheckboxesToKeepEverythingSynced_(args.syncAllDataTypes);
},
setEncryptionRadios_: function(args) {
if (args.encryptAllData) {
$('encrypt-all-option').checked = true;
this.disableEncryptionRadioGroup_();
} else {
$('encrypt-sensitive-option').checked = true;
}
if (!args.encryptAllData && !args.usePassphrase) {
$('basic-encryption-option').checked = true;
} else {
$('full-encryption-option').checked = true;
$('full-encryption-option').disabled = true;
$('basic-encryption-option').disabled = true;
}
},
setPassphraseRadios_: function(args) {
if (args.usePassphrase) {
$('explicit-option').checked = true;
// The passphrase, once set, cannot be unset, but we show a reset link.
$('explicit-option').disabled = true;
$('google-option').disabled = true;
$('sync-custom-passphrase').hidden = true;
} else {
$('google-option').checked = true;
}
},
setCheckboxesAndErrors_: function(args) {
this.setChooseDataTypesCheckboxes_(args);
this.setEncryptionRadios_(args);
this.setPassphraseRadios_(args);
},
showConfigure_: function(args) {
var datatypeSelect = $('sync-select-datatypes');
var self = this;
datatypeSelect.onchange = function() {
var syncAll = this.selectedIndex == 0;
self.setCheckboxesToKeepEverythingSynced_(syncAll);
};
this.resetPage_('sync-setup-configure');
$('sync-setup-configure').hidden = false;
// onsubmit is changed when submitting a passphrase. Reset it to its
// default.
$('choose-data-types-form').onsubmit = function() {
self.sendConfiguration_();
return false;
};
if (args) {
this.setCheckboxesAndErrors_(args);
this.useEncryptEverything_ = args.encryptAllData;
// Whether to display the 'Sync everything' confirmation page or the
// customize data types page.
var syncAllDataTypes = args.syncAllDataTypes;
this.usePassphrase_ = args.usePassphrase;
this.keystoreEncryptionEnabled_ = args.keystoreEncryptionEnabled;
if (args.showSyncEverythingPage == false || this.usePassphrase_ ||
syncAllDataTypes == false || args.showPassphrase) {
this.showCustomizePage_(args, syncAllDataTypes);
} else {
this.showSyncEverythingPage_();
}
}
},
showSpinner_: function() {
this.resetPage_('sync-setup-spinner');
$('sync-setup-spinner').hidden = false;
},
showTimeoutPage_: function() {
this.resetPage_('sync-setup-timeout');
$('sync-setup-timeout').hidden = false;
},
showSyncEverythingPage_: function() {
$('confirm-sync-preferences').hidden = false;
$('customize-sync-preferences').hidden = true;
// Reset the selection to 'Sync everything'.
$('sync-select-datatypes').selectedIndex = 0;
// The default state is to sync everything.
this.setCheckboxesToKeepEverythingSynced_(true);
// Encrypt passwords is the default, but don't set it if the previously
// synced account is already set to encrypt everything.
if (!this.useEncryptEverything_)
$('encrypt-sensitive-option').checked = true;
// If the account is not synced with a custom passphrase, reset the
// passphrase radio when switching to the 'Sync everything' page.
if (!this.usePassphrase_) {
$('google-option').checked = true;
$('sync-custom-passphrase').hidden = true;
}
if (!this.useEncryptEverything_ && !this.usePassphrase_)
$('basic-encryption-option').checked = true;
$('confirm-everything-ok').focus();
},
/**
* Reveals the UI for entering a custom passphrase during initial setup.
* This happens if the user has previously enabled a custom passphrase on a
* different machine.
* @param {Array} args The args that contain the passphrase UI
* configuration.
* @private
*/
showPassphraseContainer_: function(args) {
// Once we require a passphrase, we prevent the user from returning to
// the Sync Everything pane.
$('use-default-link').hidden = true;
$('sync-custom-passphrase-container').hidden = true;
$('sync-existing-passphrase-container').hidden = false;
// Hide the selection options within the new encryption section when
// prompting for a passphrase.
$('sync-new-encryption-section-container').hidden = true;
$('normal-body').hidden = true;
$('google-passphrase-needed-body').hidden = true;
// Display the correct prompt to the user depending on what type of
// passphrase is needed.
if (args.usePassphrase)
$('normal-body').hidden = false;
else
$('google-passphrase-needed-body').hidden = false;
$('passphrase-learn-more').hidden = false;
// Warn the user about their incorrect passphrase if we need a passphrase
// and the passphrase field is non-empty (meaning they tried to set it
// previously but failed).
$('incorrect-passphrase').hidden =
!(args.usePassphrase && args.passphraseFailed);
$('sync-passphrase-warning').hidden = false;
$('passphrase').focus();
},
/** @private */
showCustomizePage_: function(args, syncEverything) {
$('confirm-sync-preferences').hidden = true;
$('customize-sync-preferences').hidden = false;
$('sync-custom-passphrase-container').hidden = false;
if (this.keystoreEncryptionEnabled_) {
$('customize-sync-encryption').hidden = true;
$('sync-custom-passphrase-options').hidden = true;
$('sync-new-encryption-section-container').hidden = false;
$('customize-sync-encryption-new').hidden = false;
} else {
$('customize-sync-encryption').hidden = false;
$('sync-custom-passphrase-options').hidden = false;
$('customize-sync-encryption-new').hidden = true;
}
$('sync-existing-passphrase-container').hidden = true;
// If the user has selected the 'Customize' page on initial set up, it's
// likely he intends to change the data types. Select the
// 'Choose data types' option in this case.
var index = syncEverything ? 0 : 1;
$('sync-select-datatypes').selectedIndex = index;
this.setDataTypeCheckboxesEnabled_(!syncEverything);
// The passphrase input may need to take over focus from the OK button, so
// set focus before that logic.
$('choose-datatypes-ok').focus();
if (args && args.showPassphrase) {
this.showPassphraseContainer_(args);
} else {
// We only show the 'Use Default' link if we're not prompting for an
// existing passphrase.
var self = this;
this.animateDisableLink_($('use-default-link'), false, function() {
self.showSyncEverythingPage_();
});
}
},
/**
* Shows the appropriate sync setup page.
* @param {string} page A page of the sync setup to show.
* @param {object} args Data from the C++ to forward on to the right
* section.
*/
showSyncSetupPage_: function(page, args) {
this.setThrobbersVisible_(false);
// Hide an existing visible overlay (ensuring the close button is not
// hidden).
var children = document.querySelectorAll(
'#sync-setup-overlay > *:not(.close-button)');
for (var i = 0; i < children.length; i++)
children[i].hidden = true;
this.setInputElementsDisabledState_(false);
// If new passphrase bodies are present, overwrite the existing ones.
if (args && args.enterPassphraseBody != undefined)
$('normal-body').innerHTML = args.enterPassphraseBody;
if (args && args.enterGooglePassphraseBody != undefined) {
$('google-passphrase-needed-body').innerHTML =
args.enterGooglePassphraseBody;
}
if (args && args.fullEncryptionBody != undefined)
$('full-encryption-body').innerHTML = args.fullEncryptionBody;
// NOTE: Because both showGaiaLogin_() and showConfigure_() change the
// focus, we need to ensure that the overlay container and dialog aren't
// [hidden] (as trying to focus() nodes inside of a [hidden] DOM section
// doesn't work).
if (page == 'done')
this.closeOverlay_();
else
this.showOverlay_();
if (page == 'login')
this.showGaiaLogin_(args);
else if (page == 'configure' || page == 'passphrase')
this.showConfigure_(args);
else if (page == 'spinner')
this.showSpinner_();
else if (page == 'timeout')
this.showTimeoutPage_();
},
/**
* Changes the visibility of throbbers on this page.
* @param {boolean} visible Whether or not to set all throbber nodes
* visible.
*/
setThrobbersVisible_: function(visible) {
var throbbers = document.getElementsByClassName('throbber');
for (var i = 0; i < throbbers.length; i++)
throbbers[i].style.visibility = visible ? 'visible' : 'hidden';
},
/**
* Set the appropriate focus on the GAIA login section of the overlay.
* @private
*/
loginSetFocus_: function() {
var email = this.getLoginEmail_();
if (email && !email.value) {
email.focus();
return;
}
var passwd = this.getLoginPasswd_();
if (passwd)
passwd.focus();
},
/**
* Get the login email text input DOM element.
* @return {DOMElement} The login email text input.
* @private
*/
getLoginEmail_: function() {
return $('gaia-email');
},
/**
* Get the login password text input DOM element.
* @return {DOMElement} The login password text input.
* @private
*/
getLoginPasswd_: function() {
return $('gaia-passwd');
},
/**
* Get the sign in button DOM element.
* @return {DOMElement} The sign in button.
* @private
*/
getSignInButton_: function() {
return $('sign-in');
},
showAccessCodeRequired_: function() {
this.allowEmptyPassword_ = true;
$('password-row').hidden = true;
$('email-row').hidden = true;
$('otp-input-row').hidden = true;
$('access-code-input-row').hidden = false;
$('access-code').disabled = false;
$('access-code').focus();
},
showOtpRequired_: function() {
this.allowEmptyPassword_ = true;
$('password-row').hidden = true;
$('email-row').hidden = true;
$('access-code-input-row').hidden = true;
$('otp-input-row').hidden = false;
$('otp').disabled = false;
$('otp').focus();
},
showCaptcha_: function(args) {
this.allowEmptyPassword_ = args.hideEmailAndPassword;
this.captchaChallengeActive_ = true;
if (args.hideEmailAndPassword) {
$('password-row').hidden = true;
$('email-row').hidden = true;
$('create-account-div').hidden = true;
} else {
// The captcha takes up lots of space, so make room.
$('top-blurb-error').hidden = true;
$('create-account-div').hidden = true;
$('gaia-email').disabled = true;
$('gaia-passwd').disabled = false;
}
// It's showtime for the captcha now.
$('captcha-div').hidden = false;
$('captcha-value').disabled = false;
$('captcha-wrapper').style.backgroundImage = url(args.captchaUrl);
},
/**
* Reset the state of all descendant elements of a root element to their
* initial state.
* The initial state is specified by adding a class to the descendant
* element in sync_setup_overlay.html.
* @param {HTMLElement} pageElementId The root page element id.
* @private
*/
resetPage_: function(pageElementId) {
var page = $(pageElementId);
var forEach = function(arr, fn) {
var length = arr.length;
for (var i = 0; i < length; i++) {
fn(arr[i]);
}
};
forEach(page.getElementsByClassName('reset-hidden'),
function(elt) { elt.hidden = true; });
forEach(page.getElementsByClassName('reset-shown'),
function(elt) { elt.hidden = false; });
forEach(page.getElementsByClassName('reset-disabled'),
function(elt) { elt.disabled = true; });
forEach(page.getElementsByClassName('reset-enabled'),
function(elt) { elt.disabled = false; });
forEach(page.getElementsByClassName('reset-value'),
function(elt) { elt.value = ''; });
forEach(page.getElementsByClassName('reset-opaque'),
function(elt) { elt.classList.remove('transparent'); });
},
showGaiaLogin_: function(args) {
var oldAccessCodeValue = $('access-code').value;
this.resetPage_('sync-setup-login');
$('sync-setup-login').hidden = false;
this.allowEmptyPassword_ = false;
this.captchaChallengeActive_ = false;
this.lastEmailAddress_ = args.lastEmailAddress;
var f = $('gaia-login-form');
var email = $('gaia-email');
var passwd = $('gaia-passwd');
if (f) {
if (args.user != undefined) {
if (email.value != args.user)
passwd.value = ''; // Reset the password field
email.value = args.user;
}
if (!args.editableUser) {
$('email-row').hidden = true;
var span = $('email-readonly');
span.textContent = email.value;
$('email-readonly-row').hidden = false;
$('create-account-div').hidden = true;
}
f.accessCode.disabled = true;
f.otp.disabled = true;
}
if (1 == args.error) {
if (oldAccessCodeValue) {
$('errormsg-0-access-code').hidden = false;
this.showAccessCodeRequired_();
} else {
$('errormsg-1-password').hidden = (args.errorMessage != undefined);
}
this.setBlurbError_(args.errorMessage);
} else if (3 == args.error) {
$('errormsg-0-connection').hidden = false;
this.setBlurbError_(args.errorMessage);
} else if (4 == args.error) {
this.showCaptcha_(args);
} else if (7 == args.error) {
this.setBlurbError_(loadTimeData.getString('serviceUnavailableError'));
} else if (8 == args.error) {
if (args.askForOtp) {
this.showOtpRequired_();
} else {
if (oldAccessCodeValue)
$('errormsg-0-access-code').hidden = false;
this.showAccessCodeRequired_();
}
} else if (args.errorMessage) {
this.setBlurbError_(args.errorMessage);
}
if (args.fatalError) {
$('errormsg-fatal').hidden = false;
$('sign-in').disabled = true;
return;
}
$('sign-in').disabled = false;
$('sign-in').value = loadTimeData.getString('signin');
this.loginSetFocus_();
},
resetErrorVisibility_: function() {
$('errormsg-0-email').hidden = true;
$('errormsg-0-password').hidden = true;
$('errormsg-1-password').hidden = true;
$('errormsg-different-email').hidden = true;
$('errormsg-0-connection').hidden = true;
$('errormsg-0-access-code').hidden = true;
$('errormsg-0-otp').hidden = true;
},
setBlurbError_: function(errorMessage) {
if (this.captchaChallengeActive_)
return; // No blurb in captcha challenge mode.
if (errorMessage) {
$('error-signing-in').hidden = true;
$('error-custom').hidden = false;
$('error-custom').textContent = errorMessage;
} else {
$('error-signing-in').hidden = false;
$('error-custom').hidden = true;
}
$('top-blurb-error').hidden = false;
$('gaia-email').disabled = false;
$('gaia-passwd').disabled = false;
},
matchesASPRegex_: function(toMatch) {
var noSpaces = /[a-z]{16}/;
var withSpaces = /([a-z]{4}\s){3}[a-z]{4}/;
if (toMatch.match(noSpaces) || toMatch.match(withSpaces))
return true;
return false;
},
setErrorVisibility_: function() {
var errormsgDifferentEmail = $('errormsg-different-email');
var isErrormsgDifferentEmailHidden = errormsgDifferentEmail.hidden;
this.resetErrorVisibility_();
var f = $('gaia-login-form');
var email = $('gaia-email');
var passwd = $('gaia-passwd');
if (!email.value) {
$('errormsg-0-email').hidden = false;
this.setBlurbError_();
return false;
}
// If email is different from last email, and we have not already warned
// the user, tell them now. Otherwise proceed as usual. When comparing
// email ids, use @gmail.com as the domain if not provided.
function normalized_email(id) {
return ((id.indexOf('@') != -1) ? id : id + '@gmail.com');
}
if (this.lastEmailAddress_.length > 0 &&
normalized_email(email.value) !=
normalized_email(this.lastEmailAddress_) &&
isErrormsgDifferentEmailHidden) {
errormsgDifferentEmail.hidden = false;
return false;
}
// Don't enforce password being non-blank when checking access code (it
// will have been cleared when the page was displayed).
if (!this.allowEmptyPassword_ && !passwd.value) {
$('errormsg-0-password').hidden = false;
this.setBlurbError_();
return false;
}
if (!f.accessCode.disabled && !f.accessCode.value) {
$('errormsg-0-access-code').hidden = false;
return false;
}
if (f.accessCode.disabled && this.matchesASPRegex_(passwd.value) &&
$('asp-warning-div').hidden) {
$('asp-warning-div').hidden = false;
$('gaia-passwd').value = '';
return false;
}
if (!f.otp.disabled && !f.otp.value) {
$('errormsg-0-otp').hidden = false;
return false;
}
return true;
},
sendCredentialsAndClose_: function() {
if (!this.setErrorVisibility_()) {
return false;
}
$('gaia-email').disabled = true;
$('gaia-passwd').disabled = true;
$('captcha-value').disabled = true;
$('access-code').disabled = true;
$('otp').disabled = true;
this.setThrobbersVisible_(true);
var f = $('gaia-login-form');
var email = $('gaia-email');
var passwd = $('gaia-passwd');
var result = JSON.stringify({'user': email.value,
'pass': passwd.value,
'captcha': f.captchaValue.value,
'otp': f.otp.value,
'accessCode': f.accessCode.value
});
$('sign-in').disabled = true;
chrome.send('SyncSetupSubmitAuth', [result]);
},
showSuccessAndClose_: function() {
$('sign-in').value = loadTimeData.getString('loginSuccess');
setTimeout(this.closeOverlay_, 1600);
},
showSuccessAndSettingUp_: function() {
$('sign-in').value = loadTimeData.getString('settingUp');
this.setThrobbersVisible_(true);
$('top-blurb-error').hidden = true;
},
/**
* Displays the stop syncing dialog.
* @private
*/
showStopSyncingUI_: function() {
// Hide any visible children of the overlay.
var overlay = $('sync-setup-overlay');
for (var i = 0; i < overlay.children.length; i++)
overlay.children[i].hidden = true;
// Bypass OptionsPage.navigateToPage because it will call didShowPage
// which will set its own visible page, based on the flow state.
this.visible = true;
$('sync-setup-stop-syncing').hidden = false;
$('stop-syncing-cancel').focus();
},
/**
* Steps into the appropriate Sync Setup error UI.
* @private
*/
showErrorUI_: function() {
chrome.send('SyncSetupShowErrorUI');
},
/**
* Determines the appropriate page to show in the Sync Setup UI based on
* the state of the Sync backend.
* @private
*/
showSetupUI_: function() {
chrome.send('SyncSetupShowSetupUI');
},
/**
* Shows advanced configuration UI, skipping the login dialog.
* @private
*/
showSetupUIWithoutLogin_: function() {
chrome.send('SyncSetupShowSetupUIWithoutLogin');
},
/**
* Forces user to sign out of Chrome for Chrome OS.
* @private
*/
doSignOutOnAuthError_: function() {
chrome.send('SyncSetupDoSignOutOnAuthError');
},
/**
* Hides the outer elements of the login UI. This is used by the sync promo
* to customize the look of the login box.
*/
hideOuterLoginUI_: function() {
$('sync-setup-overlay-title').hidden = true;
$('sync-setup-cancel').hidden = true;
}
};
// These get methods should only be called by the WebUI tests.
SyncSetupOverlay.getLoginEmail = function() {
return SyncSetupOverlay.getInstance().getLoginEmail_();
};
SyncSetupOverlay.getLoginPasswd = function() {
return SyncSetupOverlay.getInstance().getLoginPasswd_();
};
SyncSetupOverlay.getSignInButton = function() {
return SyncSetupOverlay.getInstance().getSignInButton_();
};
// These methods are for general consumption.
SyncSetupOverlay.showErrorUI = function() {
SyncSetupOverlay.getInstance().showErrorUI_();
};
SyncSetupOverlay.showSetupUI = function() {
SyncSetupOverlay.getInstance().showSetupUI_();
};
SyncSetupOverlay.showSetupUIWithoutLogin = function() {
SyncSetupOverlay.getInstance().showSetupUIWithoutLogin_();
};
SyncSetupOverlay.doSignOutOnAuthError = function() {
SyncSetupOverlay.getInstance().doSignOutOnAuthError_();
};
SyncSetupOverlay.showSyncSetupPage = function(page, args) {
SyncSetupOverlay.getInstance().showSyncSetupPage_(page, args);
};
SyncSetupOverlay.showSuccessAndClose = function() {
SyncSetupOverlay.getInstance().showSuccessAndClose_();
};
SyncSetupOverlay.showSuccessAndSettingUp = function() {
SyncSetupOverlay.getInstance().showSuccessAndSettingUp_();
};
SyncSetupOverlay.showStopSyncingUI = function() {
SyncSetupOverlay.getInstance().showStopSyncingUI_();
};
// Export
return {
SyncSetupOverlay: SyncSetupOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview A collection of utility methods for UberPage and its contained
* pages.
*/
cr.define('uber', function() {
/**
* Fixed position header elements on the page to be shifted by handleScroll.
* @type {NodeList}
*/
var headerElements;
/**
* This should be called by uber content pages when DOM content has loaded.
*/
function onContentFrameLoaded() {
headerElements = document.getElementsByTagName('header');
document.addEventListener('scroll', handleScroll);
// Trigger the scroll handler to tell the navigation if our page started
// with some scroll (happens when you use tab restore).
handleScroll();
window.addEventListener('message', handleWindowMessage);
}
/**
* Handles scroll events on the document. This adjusts the position of all
* headers and updates the parent frame when the page is scrolled.
* @private
*/
function handleScroll() {
var offset = document.body.scrollLeft * -1;
for (var i = 0; i < headerElements.length; i++)
headerElements[i].style.webkitTransform = 'translateX(' + offset + 'px)';
invokeMethodOnParent('adjustToScroll', document.body.scrollLeft);
};
/**
* Handles 'message' events on window.
* @param {Event} e The message event.
*/
function handleWindowMessage(e) {
if (e.data.method === 'frameSelected')
handleFrameSelected();
else if (e.data.method === 'mouseWheel')
handleMouseWheel(e.data.params);
}
/**
* This is called when a user selects this frame via the navigation bar
* frame (and is triggered via postMessage() from the uber page).
* @private
*/
function handleFrameSelected() {
document.body.scrollLeft = 0;
}
/**
* Called when a user mouse wheels (or trackpad scrolls) over the nav frame.
* The wheel event is forwarded here and we scroll the body.
* There's no way to figure out the actual scroll amount for a given delta.
* It differs for every platform and even initWebKitWheelEvent takes a
* pixel amount instead of a wheel delta. So we just choose something
* reasonable and hope no one notices the difference.
* @param {Object} params A structure that holds wheel deltas in X and Y.
*/
function handleMouseWheel(params) {
document.body.scrollTop -= params.deltaY * 49 / 120;
document.body.scrollLeft -= params.deltaX * 49 / 120;
}
/**
* Invokes a method on the parent window (UberPage). This is a convenience
* method for API calls into the uber page.
* @param {String} method The name of the method to invoke.
* @param {Object=} opt_params Optional property bag of parameters to pass to
* the invoked method.
* @private
*/
function invokeMethodOnParent(method, opt_params) {
if (window.location == window.parent.location)
return;
invokeMethodOnWindow(window.parent, method, opt_params, 'chrome://chrome');
}
/**
* Invokes a method on the target window.
* @param {String} method The name of the method to invoke.
* @param {Object=} opt_params Optional property bag of parameters to pass to
* the invoked method.
* @param {String=} opt_url The origin of the target window.
* @private
*/
function invokeMethodOnWindow(targetWindow, method, opt_params, opt_url) {
var data = {method: method, params: opt_params};
targetWindow.postMessage(data, opt_url ? opt_url : '*');
}
return {
invokeMethodOnParent: invokeMethodOnParent,
invokeMethodOnWindow: invokeMethodOnWindow,
onContentFrameLoaded: onContentFrameLoaded,
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var AddLanguageOverlay = options.AddLanguageOverlay;
var AlertOverlay = options.AlertOverlay;
var AutofillEditAddressOverlay = options.AutofillEditAddressOverlay;
var AutofillEditCreditCardOverlay = options.AutofillEditCreditCardOverlay;
var AutofillOptions = options.AutofillOptions;
var BrowserOptions = options.BrowserOptions;
var ClearBrowserDataOverlay = options.ClearBrowserDataOverlay;
var ConfirmDialog = options.ConfirmDialog;
var ContentSettingsExceptionsArea =
options.contentSettings.ContentSettingsExceptionsArea;
var ContentSettings = options.ContentSettings;
var CookiesView = options.CookiesView;
var EditDictionaryOverlay = cr.IsMac ? null : options.EditDictionaryOverlay;
var FactoryResetOverlay = options.FactoryResetOverlay;
var ManagedUserSettings = options.ManagedUserSettings;
var FontSettings = options.FontSettings;
var HandlerOptions = options.HandlerOptions;
var HomePageOverlay = options.HomePageOverlay;
var ImportDataOverlay = options.ImportDataOverlay;
var LanguageOptions = options.LanguageOptions;
var ManageProfileOverlay = options.ManageProfileOverlay;
var MediaGalleriesManager = options.MediaGalleriesManager;
var OptionsFocusManager = options.OptionsFocusManager;
var OptionsPage = options.OptionsPage;
var PasswordManager = options.PasswordManager;
var Preferences = options.Preferences;
var PreferredNetworks = options.PreferredNetworks;
var SearchEngineManager = options.SearchEngineManager;
var SearchPage = options.SearchPage;
var StartupOverlay = options.StartupOverlay;
var SyncSetupOverlay = options.SyncSetupOverlay;
/**
* DOMContentLoaded handler, sets up the page.
*/
function load() {
// Decorate the existing elements in the document.
cr.ui.decorate('input[pref][type=checkbox]', options.PrefCheckbox);
cr.ui.decorate('input[pref][type=number]', options.PrefNumber);
cr.ui.decorate('input[pref][type=radio]', options.PrefRadio);
cr.ui.decorate('input[pref][type=range]', options.PrefRange);
cr.ui.decorate('select[pref]', options.PrefSelect);
cr.ui.decorate('input[pref][type=text]', options.PrefTextField);
cr.ui.decorate('input[pref][type=url]', options.PrefTextField);
cr.ui.decorate('button[pref]', options.PrefButton);
cr.ui.decorate('#content-settings-page input[type=radio]:not(.handler-radio)',
options.ContentSettingsRadio);
cr.ui.decorate('#content-settings-page input[type=radio].handler-radio',
options.HandlersEnabledRadio);
cr.ui.decorate('span.controlled-setting-indicator',
options.ControlledSettingIndicator);
// Top level pages.
OptionsPage.register(SearchPage.getInstance());
OptionsPage.register(BrowserOptions.getInstance());
// Overlays.
OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(),
LanguageOptions.getInstance());
OptionsPage.registerOverlay(AlertOverlay.getInstance());
OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(),
AutofillOptions.getInstance());
OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(),
AutofillOptions.getInstance());
OptionsPage.registerOverlay(AutofillOptions.getInstance(),
BrowserOptions.getInstance(),
[$('autofill-settings')]);
OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(),
BrowserOptions.getInstance(),
[$('privacyClearDataButton')]);
OptionsPage.registerOverlay(
new ConfirmDialog(
'doNotTrackConfirm',
loadTimeData.getString('doNotTrackConfirmOverlayTabTitle'),
'do-not-track-confirm-overlay',
$('do-not-track-confirm-ok'),
$('do-not-track-confirm-cancel'),
$('do-not-track-enabled').pref,
$('do-not-track-enabled').metric),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(
new ConfirmDialog(
'instantConfirm',
loadTimeData.getString('instantConfirmOverlayTabTitle'),
'instantConfirmOverlay',
$('instantConfirmOk'),
$('instantConfirmCancel'),
$('instant-enabled-control').pref,
$('instant-enabled-control').metric,
'instant.confirm_dialog_shown'),
BrowserOptions.getInstance());
// 'spelling-enabled-control' element is only present on Chrome branded
// builds.
if ($('spelling-enabled-control')) {
OptionsPage.registerOverlay(
new ConfirmDialog(
'spellingConfirm',
loadTimeData.getString('spellingConfirmOverlayTabTitle'),
'spelling-confirm-overlay',
$('spelling-confirm-ok'),
$('spelling-confirm-cancel'),
$('spelling-enabled-control').pref,
$('spelling-enabled-control').metric,
'spellcheck.confirm_dialog_shown'),
BrowserOptions.getInstance());
}
OptionsPage.registerOverlay(ContentSettings.getInstance(),
BrowserOptions.getInstance(),
[$('privacyContentSettingsButton')]);
OptionsPage.registerOverlay(ContentSettingsExceptionsArea.getInstance(),
ContentSettings.getInstance());
OptionsPage.registerOverlay(CookiesView.getInstance(),
ContentSettings.getInstance(),
[$('privacyContentSettingsButton'),
$('show-cookies-button')]);
if (!cr.isMac) {
OptionsPage.registerOverlay(EditDictionaryOverlay.getInstance(),
LanguageOptions.getInstance(),
[$('edit-dictionary-button')]);
}
OptionsPage.registerOverlay(FontSettings.getInstance(),
BrowserOptions.getInstance(),
[$('fontSettingsCustomizeFontsButton')]);
if (HandlerOptions && $('manage-handlers-button')) {
OptionsPage.registerOverlay(HandlerOptions.getInstance(),
ContentSettings.getInstance(),
[$('manage-handlers-button')]);
}
OptionsPage.registerOverlay(HomePageOverlay.getInstance(),
BrowserOptions.getInstance(),
[$('change-home-page')]);
OptionsPage.registerOverlay(ImportDataOverlay.getInstance(),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(LanguageOptions.getInstance(),
BrowserOptions.getInstance(),
[$('language-button')]);
OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(),
BrowserOptions.getInstance());
if (loadTimeData.getBoolean('managedUsersEnabled')) {
OptionsPage.registerOverlay(ManagedUserSettings.getInstance(),
BrowserOptions.getInstance(),
[]);
}
OptionsPage.registerOverlay(MediaGalleriesManager.getInstance(),
ContentSettings.getInstance(),
[$('manage-galleries-button')]);
OptionsPage.registerOverlay(PasswordManager.getInstance(),
BrowserOptions.getInstance(),
[$('manage-passwords')]);
OptionsPage.registerOverlay(SearchEngineManager.getInstance(),
BrowserOptions.getInstance(),
[$('manage-default-search-engines')]);
OptionsPage.registerOverlay(StartupOverlay.getInstance(),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(),
BrowserOptions.getInstance());
if (cr.isChromeOS) {
OptionsPage.registerOverlay(AccountsOptions.getInstance(),
BrowserOptions.getInstance(),
[$('manage-accounts-button')]);
OptionsPage.registerOverlay(BluetoothOptions.getInstance(),
BrowserOptions.getInstance(),
[$('bluetooth-add-device')]);
OptionsPage.registerOverlay(BluetoothPairing.getInstance(),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(FactoryResetOverlay.getInstance(),
BrowserOptions.getInstance(),
[$('factory-reset-restart')]);
OptionsPage.registerOverlay(ChangePictureOptions.getInstance(),
BrowserOptions.getInstance(),
[$('account-picture')]);
OptionsPage.registerOverlay(DetailsInternetPage.getInstance(),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(DisplayOptions.getInstance(),
BrowserOptions.getInstance(),
[$('display-options')]);
OptionsPage.registerOverlay(KeyboardOverlay.getInstance(),
BrowserOptions.getInstance(),
[$('keyboard-settings-button')]);
OptionsPage.registerOverlay(PointerOverlay.getInstance(),
BrowserOptions.getInstance(),
[$('pointer-settings-button')]);
OptionsPage.registerOverlay(PreferredNetworks.getInstance(),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(
new OptionsPage('languageChewing',
loadTimeData.getString('languageChewingPageTabTitle'),
'languageChewingPage'),
LanguageOptions.getInstance());
OptionsPage.registerOverlay(
new OptionsPage('languageHangul',
loadTimeData.getString('languageHangulPageTabTitle'),
'languageHangulPage'),
LanguageOptions.getInstance());
OptionsPage.registerOverlay(
new OptionsPage('languageMozc',
loadTimeData.getString('languageMozcPageTabTitle'),
'languageMozcPage'),
LanguageOptions.getInstance());
OptionsPage.registerOverlay(
new OptionsPage('languagePinyin',
loadTimeData.getString('languagePinyinPageTabTitle'),
'languagePinyinPage'),
LanguageOptions.getInstance());
}
if (!cr.isWindows && !cr.isMac) {
OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(),
CertificateManager.getInstance());
OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(),
CertificateManager.getInstance());
OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(),
CertificateManager.getInstance());
OptionsPage.registerOverlay(CertificateManager.getInstance(),
BrowserOptions.getInstance(),
[$('certificatesManageButton')]);
OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(),
CertificateManager.getInstance());
}
OptionsFocusManager.getInstance().initialize();
Preferences.getInstance().initialize();
OptionsPage.initialize();
var path = document.location.pathname;
if (path.length > 1) {
// Skip starting slash and remove trailing slash (if any).
var pageName = path.slice(1).replace(/\/$/, '');
OptionsPage.showPageByName(pageName, true, {replaceState: true});
} else {
OptionsPage.showDefaultPage();
}
var subpagesNavTabs = document.querySelectorAll('.subpages-nav-tabs');
for (var i = 0; i < subpagesNavTabs.length; i++) {
subpagesNavTabs[i].onclick = function(event) {
OptionsPage.showTab(event.srcElement);
};
}
if (navigator.plugins['Shockwave Flash'])
document.documentElement.setAttribute('hasFlashPlugin', '');
window.setTimeout(function() {
document.documentElement.classList.remove('loading');
});
}
document.documentElement.classList.add('loading');
document.addEventListener('DOMContentLoaded', load);
/**
* Listener for the |beforeunload| event.
*/
window.onbeforeunload = function() {
options.OptionsPage.willClose();
};
/**
* Listener for the |popstate| event.
* @param {Event} e The |popstate| event.
*/
window.onpopstate = function(e) {
options.OptionsPage.setState(e.state);
};
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
if (document.location != 'chrome://settings-frame/options_settings_app.html')
return;
document.documentElement.classList.add('settings-app');
// Override the offset in the options page.
OptionsPage.setHorizontalOffset(38);
loadTimeData.overrideValues(loadTimeData.getValue('settingsApp'));
}());